claude-mpm 4.13.2__py3-none-any.whl → 4.18.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_ENGINEER.md +286 -0
- claude_mpm/agents/BASE_PM.md +48 -17
- claude_mpm/agents/OUTPUT_STYLE.md +329 -11
- claude_mpm/agents/PM_INSTRUCTIONS.md +227 -8
- claude_mpm/agents/agent_loader.py +17 -5
- claude_mpm/agents/frontmatter_validator.py +284 -253
- claude_mpm/agents/templates/agentic-coder-optimizer.json +9 -2
- claude_mpm/agents/templates/api_qa.json +7 -1
- claude_mpm/agents/templates/clerk-ops.json +8 -1
- claude_mpm/agents/templates/code_analyzer.json +4 -1
- claude_mpm/agents/templates/dart_engineer.json +11 -1
- claude_mpm/agents/templates/data_engineer.json +11 -1
- claude_mpm/agents/templates/documentation.json +6 -1
- claude_mpm/agents/templates/engineer.json +18 -1
- claude_mpm/agents/templates/gcp_ops_agent.json +8 -1
- claude_mpm/agents/templates/golang_engineer.json +11 -1
- claude_mpm/agents/templates/java_engineer.json +12 -2
- claude_mpm/agents/templates/local_ops_agent.json +1217 -6
- claude_mpm/agents/templates/nextjs_engineer.json +11 -1
- claude_mpm/agents/templates/ops.json +8 -1
- claude_mpm/agents/templates/php-engineer.json +11 -1
- claude_mpm/agents/templates/project_organizer.json +10 -3
- claude_mpm/agents/templates/prompt-engineer.json +5 -1
- claude_mpm/agents/templates/python_engineer.json +11 -1
- claude_mpm/agents/templates/qa.json +7 -1
- claude_mpm/agents/templates/react_engineer.json +11 -1
- claude_mpm/agents/templates/refactoring_engineer.json +8 -1
- claude_mpm/agents/templates/research.json +4 -1
- claude_mpm/agents/templates/ruby-engineer.json +11 -1
- claude_mpm/agents/templates/rust_engineer.json +11 -1
- claude_mpm/agents/templates/security.json +6 -1
- claude_mpm/agents/templates/svelte-engineer.json +225 -0
- claude_mpm/agents/templates/ticketing.json +6 -1
- claude_mpm/agents/templates/typescript_engineer.json +11 -1
- claude_mpm/agents/templates/vercel_ops_agent.json +8 -1
- claude_mpm/agents/templates/version_control.json +8 -1
- claude_mpm/agents/templates/web_qa.json +7 -1
- claude_mpm/agents/templates/web_ui.json +11 -1
- claude_mpm/cli/__init__.py +34 -706
- claude_mpm/cli/commands/agent_manager.py +25 -12
- claude_mpm/cli/commands/agent_state_manager.py +186 -0
- claude_mpm/cli/commands/agents.py +204 -148
- claude_mpm/cli/commands/aggregate.py +7 -3
- claude_mpm/cli/commands/analyze.py +9 -4
- claude_mpm/cli/commands/analyze_code.py +7 -2
- claude_mpm/cli/commands/auto_configure.py +7 -9
- claude_mpm/cli/commands/config.py +47 -13
- claude_mpm/cli/commands/configure.py +294 -1788
- 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/local_deploy.py +537 -0
- claude_mpm/cli/commands/memory.py +54 -20
- claude_mpm/cli/commands/mpm_init.py +39 -25
- claude_mpm/cli/commands/mpm_init_handler.py +8 -3
- claude_mpm/cli/executor.py +202 -0
- claude_mpm/cli/helpers.py +105 -0
- claude_mpm/cli/interactive/__init__.py +3 -0
- claude_mpm/cli/interactive/skills_wizard.py +491 -0
- claude_mpm/cli/parsers/__init__.py +7 -1
- claude_mpm/cli/parsers/base_parser.py +98 -3
- claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
- claude_mpm/cli/shared/output_formatters.py +28 -19
- claude_mpm/cli/startup.py +481 -0
- claude_mpm/cli/utils.py +52 -1
- claude_mpm/commands/mpm-help.md +3 -0
- claude_mpm/commands/mpm-version.md +113 -0
- claude_mpm/commands/mpm.md +1 -0
- claude_mpm/config/agent_config.py +2 -2
- claude_mpm/config/model_config.py +428 -0
- claude_mpm/core/base_service.py +13 -12
- claude_mpm/core/enums.py +452 -0
- claude_mpm/core/factories.py +1 -1
- claude_mpm/core/instruction_reinforcement_hook.py +2 -1
- claude_mpm/core/interactive_session.py +9 -3
- claude_mpm/core/logging_config.py +6 -2
- claude_mpm/core/oneshot_session.py +8 -4
- claude_mpm/core/optimized_agent_loader.py +3 -3
- claude_mpm/core/output_style_manager.py +12 -192
- claude_mpm/core/service_registry.py +5 -1
- claude_mpm/core/types.py +2 -9
- claude_mpm/core/typing_utils.py +7 -6
- claude_mpm/dashboard/static/js/dashboard.js +0 -14
- claude_mpm/dashboard/templates/index.html +3 -41
- claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
- claude_mpm/hooks/instruction_reinforcement.py +7 -2
- claude_mpm/models/resume_log.py +340 -0
- claude_mpm/services/agents/auto_config_manager.py +10 -11
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
- claude_mpm/services/agents/deployment/agent_record_service.py +1 -1
- claude_mpm/services/agents/deployment/agent_validator.py +17 -1
- claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
- claude_mpm/services/agents/deployment/interface_adapter.py +3 -2
- claude_mpm/services/agents/deployment/local_template_deployment.py +1 -1
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +7 -6
- 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 +5 -3
- 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/validation/__init__.py +3 -1
- claude_mpm/services/agents/deployment/validation/validation_result.py +1 -9
- claude_mpm/services/agents/local_template_manager.py +1 -1
- claude_mpm/services/agents/memory/agent_memory_manager.py +5 -2
- claude_mpm/services/agents/registry/modification_tracker.py +5 -2
- claude_mpm/services/command_handler_service.py +11 -5
- claude_mpm/services/core/interfaces/__init__.py +74 -2
- 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/restart.py +307 -0
- claude_mpm/services/core/interfaces/stability.py +260 -0
- claude_mpm/services/core/models/__init__.py +33 -0
- claude_mpm/services/core/models/agent_config.py +12 -28
- claude_mpm/services/core/models/health.py +162 -0
- claude_mpm/services/core/models/process.py +235 -0
- claude_mpm/services/core/models/restart.py +302 -0
- claude_mpm/services/core/models/stability.py +264 -0
- claude_mpm/services/core/path_resolver.py +23 -7
- claude_mpm/services/diagnostics/__init__.py +2 -2
- claude_mpm/services/diagnostics/checks/agent_check.py +25 -24
- claude_mpm/services/diagnostics/checks/claude_code_check.py +24 -23
- claude_mpm/services/diagnostics/checks/common_issues_check.py +25 -24
- claude_mpm/services/diagnostics/checks/configuration_check.py +24 -23
- claude_mpm/services/diagnostics/checks/filesystem_check.py +18 -17
- claude_mpm/services/diagnostics/checks/installation_check.py +30 -29
- claude_mpm/services/diagnostics/checks/instructions_check.py +20 -19
- claude_mpm/services/diagnostics/checks/mcp_check.py +50 -36
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +36 -31
- claude_mpm/services/diagnostics/checks/monitor_check.py +23 -22
- claude_mpm/services/diagnostics/checks/startup_log_check.py +9 -8
- claude_mpm/services/diagnostics/diagnostic_runner.py +6 -5
- claude_mpm/services/diagnostics/doctor_reporter.py +28 -25
- claude_mpm/services/diagnostics/models.py +19 -24
- 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 +7 -6
- 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 +163 -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 +9 -4
- claude_mpm/services/mcp_gateway/core/__init__.py +1 -2
- claude_mpm/services/mcp_gateway/core/base.py +18 -31
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +71 -24
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +30 -28
- claude_mpm/services/memory_hook_service.py +4 -1
- 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/daemon_manager.py +3 -2
- claude_mpm/services/monitor/handlers/dashboard.py +2 -1
- claude_mpm/services/monitor/handlers/hooks.py +2 -1
- claude_mpm/services/monitor/management/lifecycle.py +3 -2
- claude_mpm/services/monitor/server.py +2 -1
- claude_mpm/services/session_management_service.py +3 -2
- claude_mpm/services/session_manager.py +205 -1
- claude_mpm/services/shared/async_service_base.py +16 -27
- claude_mpm/services/shared/lifecycle_service_base.py +1 -14
- claude_mpm/services/socketio/handlers/__init__.py +5 -2
- claude_mpm/services/socketio/handlers/hook.py +13 -2
- claude_mpm/services/socketio/handlers/registry.py +4 -2
- claude_mpm/services/socketio/server/main.py +10 -8
- claude_mpm/services/subprocess_launcher_service.py +14 -5
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +8 -7
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +6 -5
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +8 -7
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +7 -6
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +5 -4
- claude_mpm/services/unified/config_strategies/validation_strategy.py +13 -9
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +10 -3
- claude_mpm/services/unified/deployment_strategies/local.py +6 -5
- claude_mpm/services/unified/deployment_strategies/utils.py +6 -5
- claude_mpm/services/unified/deployment_strategies/vercel.py +7 -6
- claude_mpm/services/unified/interfaces.py +3 -1
- claude_mpm/services/unified/unified_analyzer.py +14 -10
- claude_mpm/services/unified/unified_config.py +2 -1
- claude_mpm/services/unified/unified_deployment.py +9 -4
- claude_mpm/services/version_service.py +104 -1
- claude_mpm/skills/__init__.py +21 -0
- claude_mpm/skills/bundled/__init__.py +6 -0
- claude_mpm/skills/bundled/api-documentation.md +393 -0
- claude_mpm/skills/bundled/async-testing.md +571 -0
- claude_mpm/skills/bundled/code-review.md +143 -0
- claude_mpm/skills/bundled/database-migration.md +199 -0
- claude_mpm/skills/bundled/docker-containerization.md +194 -0
- claude_mpm/skills/bundled/express-local-dev.md +1429 -0
- claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
- claude_mpm/skills/bundled/git-workflow.md +414 -0
- claude_mpm/skills/bundled/imagemagick.md +204 -0
- claude_mpm/skills/bundled/json-data-handling.md +223 -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 +567 -0
- claude_mpm/skills/bundled/refactoring-patterns.md +180 -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/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/tools/code_tree_analyzer.py +177 -141
- claude_mpm/tools/code_tree_events.py +4 -2
- claude_mpm/utils/agent_dependency_loader.py +2 -2
- {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/METADATA +117 -8
- {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/RECORD +238 -174
- claude_mpm/dashboard/static/css/code-tree.css +0 -1639
- claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +0 -353
- claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +0 -235
- claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +0 -409
- claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +0 -435
- claude_mpm/dashboard/static/js/components/code-tree.js +0 -5869
- claude_mpm/dashboard/static/js/components/code-viewer.js +0 -1386
- claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +0 -425
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +0 -1041
- claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +0 -347
- claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +0 -575
- claude_mpm/services/project/analyzer_refactored.py +0 -450
- {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/WHEEL +0 -0
- {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/top_level.txt +0 -0
|
@@ -1,1386 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Code Viewer Component - File Activity Tree Viewer
|
|
3
|
-
*
|
|
4
|
-
* Shows a D3.js tree visualization of files that have been viewed or edited,
|
|
5
|
-
* including AST paths (classes, functions, methods) extracted from the files.
|
|
6
|
-
* This is NOT a directory viewer but an activity-focused visualization.
|
|
7
|
-
* Renders in the File Tree tab of the dashboard.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
class CodeViewer {
|
|
11
|
-
constructor() {
|
|
12
|
-
this.container = null;
|
|
13
|
-
this.svg = null;
|
|
14
|
-
this.initialized = false;
|
|
15
|
-
this.fileActivity = new Map(); // Map of file path to activity data
|
|
16
|
-
this.sessions = new Map();
|
|
17
|
-
this.currentSession = null;
|
|
18
|
-
this.treeData = null;
|
|
19
|
-
this.d3Tree = null;
|
|
20
|
-
this.d3Root = null;
|
|
21
|
-
this.selectedNode = null;
|
|
22
|
-
this.width = 800;
|
|
23
|
-
this.height = 600;
|
|
24
|
-
this.nodeRadius = 5;
|
|
25
|
-
this.renderInProgress = false; // Prevent concurrent renders
|
|
26
|
-
this.containerObserver = null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Initialize the code viewer
|
|
31
|
-
*/
|
|
32
|
-
initialize() {
|
|
33
|
-
console.log('[CodeViewer] initialize() called');
|
|
34
|
-
if (this.initialized) {
|
|
35
|
-
console.log('[CodeViewer] Already initialized, skipping');
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
console.log('[CodeViewer] Starting initialization...');
|
|
40
|
-
try {
|
|
41
|
-
// Initialize components
|
|
42
|
-
this.setupContainer();
|
|
43
|
-
console.log('[CodeViewer] Container setup complete');
|
|
44
|
-
|
|
45
|
-
this.setupEventHandlers();
|
|
46
|
-
console.log('[CodeViewer] Event handlers setup complete');
|
|
47
|
-
|
|
48
|
-
this.subscribeToEvents();
|
|
49
|
-
console.log('[CodeViewer] Event subscription complete');
|
|
50
|
-
|
|
51
|
-
this.processExistingEvents();
|
|
52
|
-
console.log('[CodeViewer] Existing events processed');
|
|
53
|
-
|
|
54
|
-
this.initialized = true;
|
|
55
|
-
console.log('[CodeViewer] Initialization complete!');
|
|
56
|
-
} catch (error) {
|
|
57
|
-
console.error('[CodeViewer] Error during initialization:', error);
|
|
58
|
-
throw error;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Setup the container in the File Tree tab
|
|
64
|
-
*/
|
|
65
|
-
setupContainer() {
|
|
66
|
-
// Find the File Tree tab container
|
|
67
|
-
const treeContainer = document.getElementById('claude-tree-container');
|
|
68
|
-
if (!treeContainer) {
|
|
69
|
-
console.error('File Tree container not found');
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Store the container reference
|
|
74
|
-
this.container = treeContainer;
|
|
75
|
-
|
|
76
|
-
// Setup the activity tree interface
|
|
77
|
-
this.renderInterface();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Render the activity tree interface in the File Tree tab
|
|
82
|
-
*/
|
|
83
|
-
renderInterface() {
|
|
84
|
-
if (!this.container) {
|
|
85
|
-
console.error('[CodeViewer] Container not found, cannot render interface');
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Prevent concurrent renders
|
|
90
|
-
if (this.renderInProgress) {
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Check if interface already exists and is intact
|
|
95
|
-
const existingWrapper = this.container.querySelector('.activity-tree-wrapper');
|
|
96
|
-
const existingEmptyState = this.container.querySelector('.file-tree-empty-state');
|
|
97
|
-
const existingSvg = this.container.querySelector('#claude-activity-tree-svg');
|
|
98
|
-
|
|
99
|
-
// Always show the tree interface, even if empty
|
|
100
|
-
// We'll show at least a session root node
|
|
101
|
-
// Remove the empty state check - we always want the tree
|
|
102
|
-
if (false) { // Disabled empty state - always show tree
|
|
103
|
-
// Only render empty state if it doesn't exist
|
|
104
|
-
if (!existingEmptyState) {
|
|
105
|
-
this.renderInProgress = true;
|
|
106
|
-
|
|
107
|
-
// Temporarily disconnect observer to prevent loops
|
|
108
|
-
if (this.containerObserver) {
|
|
109
|
-
this.containerObserver.disconnect();
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Clear any existing content completely
|
|
113
|
-
this.container.innerHTML = '';
|
|
114
|
-
|
|
115
|
-
// Show empty state
|
|
116
|
-
this.container.innerHTML = `
|
|
117
|
-
<div class="file-tree-empty-state" style="
|
|
118
|
-
text-align: center;
|
|
119
|
-
padding: 50px 20px;
|
|
120
|
-
color: #666;
|
|
121
|
-
font-family: monospace;
|
|
122
|
-
height: 100%;
|
|
123
|
-
display: flex;
|
|
124
|
-
flex-direction: column;
|
|
125
|
-
justify-content: center;
|
|
126
|
-
align-items: center;
|
|
127
|
-
background: #fafafa;
|
|
128
|
-
">
|
|
129
|
-
<h2 style="color: #333; margin-bottom: 20px; font-size: 24px;">
|
|
130
|
-
📁 File Activity Tree
|
|
131
|
-
</h2>
|
|
132
|
-
<p style="margin-bottom: 15px; font-size: 16px;">
|
|
133
|
-
No file operations recorded yet.
|
|
134
|
-
</p>
|
|
135
|
-
<p style="font-size: 14px; color: #888;">
|
|
136
|
-
The tree will appear here when files are:
|
|
137
|
-
</p>
|
|
138
|
-
<ul style="
|
|
139
|
-
list-style: none;
|
|
140
|
-
padding: 20px 30px;
|
|
141
|
-
text-align: left;
|
|
142
|
-
display: inline-block;
|
|
143
|
-
background: white;
|
|
144
|
-
border: 1px solid #e0e0e0;
|
|
145
|
-
border-radius: 5px;
|
|
146
|
-
margin-top: 10px;
|
|
147
|
-
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
148
|
-
">
|
|
149
|
-
<li style="margin: 8px 0;">📖 Read (using Read tool)</li>
|
|
150
|
-
<li style="margin: 8px 0;">✏️ Edited (using Edit tool)</li>
|
|
151
|
-
<li style="margin: 8px 0;">💾 Written (using Write tool)</li>
|
|
152
|
-
<li style="margin: 8px 0;">📝 Multi-edited (using MultiEdit tool)</li>
|
|
153
|
-
<li style="margin: 8px 0;">📓 Notebook edited (using NotebookEdit tool)</li>
|
|
154
|
-
</ul>
|
|
155
|
-
<p style="margin-top: 20px; font-size: 12px; color: #999;">
|
|
156
|
-
D3.js tree visualization will render automatically when file operations occur
|
|
157
|
-
</p>
|
|
158
|
-
</div>
|
|
159
|
-
`;
|
|
160
|
-
|
|
161
|
-
// Mark render as complete and re-enable observer if needed
|
|
162
|
-
this.renderInProgress = false;
|
|
163
|
-
|
|
164
|
-
// Re-enable container protection after render
|
|
165
|
-
if (this.containerObserver && this.container) {
|
|
166
|
-
this.containerObserver.observe(this.container, {
|
|
167
|
-
childList: true,
|
|
168
|
-
subtree: false // Only watch direct children, not subtree
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// If we have file activity and the interface already exists, skip
|
|
176
|
-
if (existingWrapper && existingSvg) {
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
this.renderInProgress = true;
|
|
181
|
-
|
|
182
|
-
// Temporarily disconnect observer to prevent loops
|
|
183
|
-
if (this.containerObserver) {
|
|
184
|
-
this.containerObserver.disconnect();
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Clear any existing content completely
|
|
188
|
-
this.container.innerHTML = '';
|
|
189
|
-
|
|
190
|
-
// Create the activity tree interface (without redundant session selector)
|
|
191
|
-
this.container.innerHTML = `
|
|
192
|
-
<div class="activity-tree-wrapper" style="height: 100%; display: flex; flex-direction: column;">
|
|
193
|
-
<div class="activity-controls" style="padding: 10px; border-bottom: 1px solid #ddd; background: #f9f9f9; display: flex; align-items: center; gap: 10px;">
|
|
194
|
-
<button id="claude-expand-all-btn" class="control-btn" style="padding: 4px 8px; font-size: 0.9em;">Expand All</button>
|
|
195
|
-
<button id="claude-collapse-all-btn" class="control-btn" style="padding: 4px 8px; font-size: 0.9em;">Collapse All</button>
|
|
196
|
-
<button id="claude-reset-zoom-btn" class="control-btn" style="padding: 4px 8px; font-size: 0.9em;">Reset Zoom</button>
|
|
197
|
-
<div class="stats" id="claude-tree-stats" style="margin-left: auto; font-size: 0.9em; color: #666;"></div>
|
|
198
|
-
</div>
|
|
199
|
-
<div class="tree-container" id="claude-tree-svg-container" style="flex: 1; overflow: hidden; position: relative; background: white;">
|
|
200
|
-
<svg id="claude-activity-tree-svg" style="width: 100%; height: 100%;"></svg>
|
|
201
|
-
</div>
|
|
202
|
-
<div class="legend" style="padding: 5px 10px; border-top: 1px solid #ddd; background: #f9f9f9; font-size: 0.85em; display: flex; gap: 15px;">
|
|
203
|
-
<span class="legend-item"><span style="color: #4CAF50;">●</span> File</span>
|
|
204
|
-
<span class="legend-item"><span style="color: #2196F3;">●</span> Class</span>
|
|
205
|
-
<span class="legend-item"><span style="color: #FF9800;">●</span> Function</span>
|
|
206
|
-
<span class="legend-item"><span style="color: #9C27B0;">●</span> Method</span>
|
|
207
|
-
<span class="legend-item"><span style="color: #F44336;">◆</span> Edited</span>
|
|
208
|
-
<span class="legend-item"><span style="color: #4CAF50;">○</span> Viewed</span>
|
|
209
|
-
</div>
|
|
210
|
-
</div>
|
|
211
|
-
`;
|
|
212
|
-
|
|
213
|
-
// Get container dimensions for tree sizing
|
|
214
|
-
const svgContainer = document.getElementById('claude-tree-svg-container');
|
|
215
|
-
if (svgContainer) {
|
|
216
|
-
const rect = svgContainer.getBoundingClientRect();
|
|
217
|
-
this.width = rect.width || 800;
|
|
218
|
-
this.height = rect.height || 600;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Mark render as complete and re-enable observer if needed
|
|
222
|
-
this.renderInProgress = false;
|
|
223
|
-
|
|
224
|
-
// Re-enable container protection after render
|
|
225
|
-
if (this.containerObserver && this.container) {
|
|
226
|
-
this.containerObserver.observe(this.container, {
|
|
227
|
-
childList: true,
|
|
228
|
-
subtree: false // Only watch direct children, not subtree
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Render the content without switching tabs
|
|
235
|
-
* This is called by UIStateManager when the tab is already active
|
|
236
|
-
*/
|
|
237
|
-
renderContent() {
|
|
238
|
-
this._showInternal();
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Show the activity tree (for backward compatibility)
|
|
243
|
-
* Note: Tab switching is now handled by UIStateManager
|
|
244
|
-
*/
|
|
245
|
-
show() {
|
|
246
|
-
this._showInternal();
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Internal show implementation (without tab switching)
|
|
251
|
-
*/
|
|
252
|
-
_showInternal() {
|
|
253
|
-
console.log('[CodeViewer] _showInternal() called');
|
|
254
|
-
|
|
255
|
-
// Get the file tree container
|
|
256
|
-
const claudeTreeContainer = document.getElementById('claude-tree-container');
|
|
257
|
-
if (!claudeTreeContainer) {
|
|
258
|
-
console.error('[CodeViewer] File Tree container not found!');
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
console.log('[CodeViewer] Found container, current HTML length:', claudeTreeContainer.innerHTML.length);
|
|
263
|
-
console.log('[CodeViewer] Container children:', claudeTreeContainer.children.length);
|
|
264
|
-
|
|
265
|
-
// Refresh from FileToolTracker to get latest data
|
|
266
|
-
this.refreshFromFileToolTracker();
|
|
267
|
-
|
|
268
|
-
// CRITICAL: Clear any foreign content first - more aggressive cleanup
|
|
269
|
-
const foreignSelectors = [
|
|
270
|
-
'#events-list',
|
|
271
|
-
'.events-list',
|
|
272
|
-
'.event-item',
|
|
273
|
-
'.no-events',
|
|
274
|
-
'[id*="event"]',
|
|
275
|
-
'[class*="event"]'
|
|
276
|
-
];
|
|
277
|
-
|
|
278
|
-
let foundForeign = false;
|
|
279
|
-
foreignSelectors.forEach(selector => {
|
|
280
|
-
const elements = claudeTreeContainer.querySelectorAll(selector);
|
|
281
|
-
if (elements.length > 0) {
|
|
282
|
-
console.warn(`[CodeViewer] Found ${elements.length} foreign elements matching '${selector}', removing...`);
|
|
283
|
-
elements.forEach(el => el.remove());
|
|
284
|
-
foundForeign = true;
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
if (foundForeign) {
|
|
289
|
-
console.warn('[CodeViewer] Foreign content removed, clearing container completely for fresh start');
|
|
290
|
-
claudeTreeContainer.innerHTML = '';
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// CRITICAL: Prevent other components from writing to this container
|
|
294
|
-
// Add multiple attributes to mark ownership strongly
|
|
295
|
-
claudeTreeContainer.setAttribute('data-owner', 'code-viewer');
|
|
296
|
-
claudeTreeContainer.setAttribute('data-tab-reserved', 'claude-tree');
|
|
297
|
-
claudeTreeContainer.setAttribute('data-component', 'CodeViewer');
|
|
298
|
-
|
|
299
|
-
// Store the container reference if not already set
|
|
300
|
-
if (!this.container || this.container !== claudeTreeContainer) {
|
|
301
|
-
this.container = claudeTreeContainer;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Initialize if needed (this will setup container and render interface)
|
|
305
|
-
if (!this.initialized) {
|
|
306
|
-
this.initialize();
|
|
307
|
-
} else {
|
|
308
|
-
// Always render interface - it will handle empty state or tree as needed
|
|
309
|
-
const existingWrapper = this.container.querySelector('.activity-tree-wrapper');
|
|
310
|
-
const existingEmptyState = this.container.querySelector('.file-tree-empty-state');
|
|
311
|
-
if (!existingWrapper && !existingEmptyState) {
|
|
312
|
-
this.renderInterface();
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Set up mutation observer to protect container (only if not already set)
|
|
317
|
-
if (!this.containerObserver) {
|
|
318
|
-
this.protectContainer();
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Always setup event handlers and render tree
|
|
322
|
-
// Even with no file activity, we show a session root
|
|
323
|
-
this.setupControlHandlers();
|
|
324
|
-
|
|
325
|
-
// Get current session from main selector
|
|
326
|
-
const mainSessionSelect = document.getElementById('session-select');
|
|
327
|
-
if (mainSessionSelect) {
|
|
328
|
-
this.currentSession = mainSessionSelect.value || null;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Build and render tree (will create minimal session root if no data)
|
|
332
|
-
this.buildTreeData();
|
|
333
|
-
this.renderTree();
|
|
334
|
-
|
|
335
|
-
// Update stats
|
|
336
|
-
this.updateStats();
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Protect the container from being overwritten by other components
|
|
341
|
-
*/
|
|
342
|
-
protectContainer() {
|
|
343
|
-
const container = document.getElementById('claude-tree-container');
|
|
344
|
-
if (!container) return;
|
|
345
|
-
|
|
346
|
-
// Disconnect any existing observer
|
|
347
|
-
if (this.containerObserver) {
|
|
348
|
-
this.containerObserver.disconnect();
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Flag to prevent re-render loops
|
|
352
|
-
let reRenderScheduled = false;
|
|
353
|
-
|
|
354
|
-
// Create a new observer to watch for unwanted changes
|
|
355
|
-
this.containerObserver = new MutationObserver((mutations) => {
|
|
356
|
-
for (const mutation of mutations) {
|
|
357
|
-
// Check if nodes were added that shouldn't be there
|
|
358
|
-
for (const node of mutation.addedNodes) {
|
|
359
|
-
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
360
|
-
const element = node;
|
|
361
|
-
|
|
362
|
-
// AGGRESSIVE filtering: Block ANY content that's not our tree interface or empty state
|
|
363
|
-
const isUnwantedContent = (
|
|
364
|
-
element.classList?.contains('event-item') ||
|
|
365
|
-
element.classList?.contains('events-list') ||
|
|
366
|
-
element.classList?.contains('no-events') ||
|
|
367
|
-
element.id === 'events-list' ||
|
|
368
|
-
element.id === 'agents-list' ||
|
|
369
|
-
element.id === 'tools-list' ||
|
|
370
|
-
element.id === 'files-list' ||
|
|
371
|
-
(element.textContent && (
|
|
372
|
-
element.textContent.includes('[hook]') ||
|
|
373
|
-
element.textContent.includes('hook.user_prompt') ||
|
|
374
|
-
element.textContent.includes('hook.pre_tool') ||
|
|
375
|
-
element.textContent.includes('hook.post_tool') ||
|
|
376
|
-
element.textContent.includes('Connect to Socket.IO') ||
|
|
377
|
-
element.textContent.includes('No events') ||
|
|
378
|
-
element.textContent.includes('No agent events') ||
|
|
379
|
-
element.textContent.includes('No tool events') ||
|
|
380
|
-
element.textContent.includes('No file operations')
|
|
381
|
-
)) ||
|
|
382
|
-
// Block any div without our expected classes
|
|
383
|
-
(element.tagName === 'DIV' &&
|
|
384
|
-
!element.classList?.contains('activity-tree-wrapper') &&
|
|
385
|
-
!element.classList?.contains('file-tree-empty-state') &&
|
|
386
|
-
!element.classList?.contains('activity-controls') &&
|
|
387
|
-
!element.classList?.contains('tree-container') &&
|
|
388
|
-
!element.classList?.contains('legend') &&
|
|
389
|
-
!element.classList?.contains('stats') &&
|
|
390
|
-
!element.id?.startsWith('claude-'))
|
|
391
|
-
);
|
|
392
|
-
|
|
393
|
-
if (isUnwantedContent) {
|
|
394
|
-
// Block unwanted content silently
|
|
395
|
-
|
|
396
|
-
// Remove the unwanted content immediately
|
|
397
|
-
try {
|
|
398
|
-
node.remove();
|
|
399
|
-
} catch (e) {
|
|
400
|
-
console.warn('[CodeViewer] Failed to remove unwanted node:', e);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// Schedule a single re-render if needed
|
|
404
|
-
if (!reRenderScheduled && !this.renderInProgress) {
|
|
405
|
-
reRenderScheduled = true;
|
|
406
|
-
setTimeout(() => {
|
|
407
|
-
reRenderScheduled = false;
|
|
408
|
-
if (!container.querySelector('.activity-tree-wrapper') &&
|
|
409
|
-
!container.querySelector('.file-tree-empty-state')) {
|
|
410
|
-
this.renderInterface();
|
|
411
|
-
|
|
412
|
-
// Always setup controls and tree (even if no file activity)
|
|
413
|
-
this.setupControlHandlers();
|
|
414
|
-
this.buildTreeData();
|
|
415
|
-
this.renderTree();
|
|
416
|
-
}
|
|
417
|
-
}, 50);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// Also check if our content was removed
|
|
424
|
-
if (mutation.type === 'childList' && mutation.removedNodes.length > 0) {
|
|
425
|
-
for (const node of mutation.removedNodes) {
|
|
426
|
-
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
427
|
-
const element = node;
|
|
428
|
-
if (element.classList?.contains('activity-tree-wrapper') ||
|
|
429
|
-
element.classList?.contains('file-tree-empty-state')) {
|
|
430
|
-
if (!reRenderScheduled && !this.renderInProgress) {
|
|
431
|
-
reRenderScheduled = true;
|
|
432
|
-
setTimeout(() => {
|
|
433
|
-
reRenderScheduled = false;
|
|
434
|
-
this.renderInterface();
|
|
435
|
-
|
|
436
|
-
// Always setup controls and tree (even if no file activity)
|
|
437
|
-
this.setupControlHandlers();
|
|
438
|
-
this.buildTreeData();
|
|
439
|
-
this.renderTree();
|
|
440
|
-
}, 50);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
// Start observing only direct children to reduce overhead
|
|
450
|
-
this.containerObserver.observe(container, {
|
|
451
|
-
childList: true,
|
|
452
|
-
subtree: false // Only watch direct children, not entire subtree
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
/**
|
|
457
|
-
* Setup event handlers for controls
|
|
458
|
-
*/
|
|
459
|
-
setupControlHandlers() {
|
|
460
|
-
// Listen to main session selector changes
|
|
461
|
-
const mainSessionSelect = document.getElementById('session-select');
|
|
462
|
-
if (mainSessionSelect && !mainSessionSelect.hasAttribute('data-tree-listener')) {
|
|
463
|
-
mainSessionSelect.setAttribute('data-tree-listener', 'true');
|
|
464
|
-
mainSessionSelect.addEventListener('change', (e) => {
|
|
465
|
-
this.currentSession = e.target.value || null;
|
|
466
|
-
if (this.isTabActive()) {
|
|
467
|
-
this.buildTreeData();
|
|
468
|
-
this.renderTree();
|
|
469
|
-
this.updateStats();
|
|
470
|
-
}
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
// Expand all button
|
|
475
|
-
const expandBtn = document.getElementById('claude-expand-all-btn');
|
|
476
|
-
if (expandBtn && !expandBtn.hasAttribute('data-listener')) {
|
|
477
|
-
expandBtn.setAttribute('data-listener', 'true');
|
|
478
|
-
expandBtn.addEventListener('click', () => {
|
|
479
|
-
this.expandAllNodes();
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// Collapse all button
|
|
484
|
-
const collapseBtn = document.getElementById('claude-collapse-all-btn');
|
|
485
|
-
if (collapseBtn && !collapseBtn.hasAttribute('data-listener')) {
|
|
486
|
-
collapseBtn.setAttribute('data-listener', 'true');
|
|
487
|
-
collapseBtn.addEventListener('click', () => {
|
|
488
|
-
this.collapseAllNodes();
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// Reset zoom button
|
|
493
|
-
const resetBtn = document.getElementById('claude-reset-zoom-btn');
|
|
494
|
-
if (resetBtn && !resetBtn.hasAttribute('data-listener')) {
|
|
495
|
-
resetBtn.setAttribute('data-listener', 'true');
|
|
496
|
-
resetBtn.addEventListener('click', () => {
|
|
497
|
-
this.resetZoom();
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* Setup event handlers
|
|
504
|
-
*/
|
|
505
|
-
setupEventHandlers() {
|
|
506
|
-
// Tab handling is done in show() method
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
/**
|
|
510
|
-
* Subscribe to events from socket and event bus
|
|
511
|
-
*/
|
|
512
|
-
subscribeToEvents() {
|
|
513
|
-
// Listen for claude events from socket
|
|
514
|
-
if (window.socket) {
|
|
515
|
-
window.socket.on('claude_event', (event) => {
|
|
516
|
-
|
|
517
|
-
// When we get file operation events, refresh from FileToolTracker
|
|
518
|
-
if (this.isFileOperationEvent(event) || this.isDirectFileEvent(event)) {
|
|
519
|
-
// Let FileToolTracker process the event first, then refresh our view
|
|
520
|
-
setTimeout(() => {
|
|
521
|
-
this.refreshFromFileToolTracker();
|
|
522
|
-
// Only update if the File Tree tab is active
|
|
523
|
-
if (this.isTabActive()) {
|
|
524
|
-
this.buildTreeData();
|
|
525
|
-
this.renderTree();
|
|
526
|
-
this.updateStats();
|
|
527
|
-
}
|
|
528
|
-
}, 100);
|
|
529
|
-
}
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
// Also listen for specific file events
|
|
533
|
-
window.socket.on('file:read', (data) => {
|
|
534
|
-
this.handleDirectFileEvent('Read', data);
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
window.socket.on('file:write', (data) => {
|
|
538
|
-
this.handleDirectFileEvent('Write', data);
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
window.socket.on('file:edit', (data) => {
|
|
542
|
-
this.handleDirectFileEvent('Edit', data);
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// Listen for events from event bus
|
|
547
|
-
if (window.eventBus) {
|
|
548
|
-
window.eventBus.on('claude_event', (event) => {
|
|
549
|
-
|
|
550
|
-
// Process both hook events and direct file operation events
|
|
551
|
-
if (this.isFileOperationEvent(event) || this.isDirectFileEvent(event)) {
|
|
552
|
-
this.processClaudeEvent(event);
|
|
553
|
-
// Only update if the File Tree tab is active
|
|
554
|
-
if (this.isTabActive()) {
|
|
555
|
-
this.buildTreeData();
|
|
556
|
-
this.renderTree();
|
|
557
|
-
this.updateStats();
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
/**
|
|
565
|
-
* Check if File Tree tab is active
|
|
566
|
-
*/
|
|
567
|
-
isTabActive() {
|
|
568
|
-
const claudeTreeContent = document.getElementById('claude-tree-tab');
|
|
569
|
-
return claudeTreeContent && claudeTreeContent.classList.contains('active');
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
/**
|
|
573
|
-
* Process existing events from dashboard
|
|
574
|
-
*/
|
|
575
|
-
processExistingEvents() {
|
|
576
|
-
console.log('[CodeViewer] processExistingEvents called');
|
|
577
|
-
|
|
578
|
-
// First try to use FileToolTracker data if available
|
|
579
|
-
if (window.dashboard && window.dashboard.fileToolTracker) {
|
|
580
|
-
this.refreshFromFileToolTracker();
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
// Fallback to event store if FileToolTracker not available
|
|
585
|
-
if (window.dashboard && window.dashboard.eventStore) {
|
|
586
|
-
const events = window.dashboard.eventStore.getAllEvents();
|
|
587
|
-
console.log('[CodeViewer] Fallback to eventStore, total events:', events.length);
|
|
588
|
-
|
|
589
|
-
let fileOpCount = 0;
|
|
590
|
-
let processedCount = 0;
|
|
591
|
-
|
|
592
|
-
events.forEach(event => {
|
|
593
|
-
// Log detailed info about each event for debugging
|
|
594
|
-
if (event.type === 'hook') {
|
|
595
|
-
console.log('[CodeViewer] Hook event:', {
|
|
596
|
-
subtype: event.subtype,
|
|
597
|
-
tool_name: event.data?.tool_name,
|
|
598
|
-
timestamp: event.timestamp
|
|
599
|
-
});
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
if (this.isFileOperationEvent(event)) {
|
|
603
|
-
fileOpCount++;
|
|
604
|
-
console.log('[CodeViewer] Found file operation event:', event);
|
|
605
|
-
this.processClaudeEvent(event);
|
|
606
|
-
processedCount++;
|
|
607
|
-
}
|
|
608
|
-
});
|
|
609
|
-
|
|
610
|
-
console.log('[CodeViewer] processExistingEvents summary:', {
|
|
611
|
-
totalEvents: events.length,
|
|
612
|
-
fileOperations: fileOpCount,
|
|
613
|
-
processed: processedCount,
|
|
614
|
-
currentFileActivitySize: this.fileActivity.size
|
|
615
|
-
});
|
|
616
|
-
} else {
|
|
617
|
-
console.log('[CodeViewer] No dashboard or eventStore available');
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
/**
|
|
622
|
-
* Check if an event is a file operation event
|
|
623
|
-
*/
|
|
624
|
-
isFileOperationEvent(event) {
|
|
625
|
-
// Check if this is a hook event with file operation tool
|
|
626
|
-
if (event.type === 'hook' &&
|
|
627
|
-
(event.subtype === 'pre_tool' || event.subtype === 'post_tool') &&
|
|
628
|
-
event.data && event.data.tool_name) {
|
|
629
|
-
const fileOps = ['Read', 'Write', 'Edit', 'MultiEdit', 'NotebookEdit'];
|
|
630
|
-
return fileOps.includes(event.data.tool_name);
|
|
631
|
-
}
|
|
632
|
-
return false;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
/**
|
|
636
|
-
* Check if an event is a direct file event (not wrapped in hook)
|
|
637
|
-
*/
|
|
638
|
-
isDirectFileEvent(event) {
|
|
639
|
-
// Check if this is a direct file operation event
|
|
640
|
-
if (event.type === 'file_operation' ||
|
|
641
|
-
(event.tool && ['Read', 'Write', 'Edit', 'MultiEdit', 'NotebookEdit'].includes(event.tool))) {
|
|
642
|
-
return true;
|
|
643
|
-
}
|
|
644
|
-
return false;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
/**
|
|
648
|
-
* Handle direct file events from WebSocket
|
|
649
|
-
*/
|
|
650
|
-
handleDirectFileEvent(tool_name, data) {
|
|
651
|
-
const event = {
|
|
652
|
-
type: 'file_operation',
|
|
653
|
-
tool: tool_name,
|
|
654
|
-
data: {
|
|
655
|
-
tool_name: tool_name,
|
|
656
|
-
tool_parameters: data.parameters || data,
|
|
657
|
-
tool_output: data.output || null,
|
|
658
|
-
session_id: data.session_id || this.currentSession,
|
|
659
|
-
working_directory: data.working_directory || '/'
|
|
660
|
-
},
|
|
661
|
-
timestamp: data.timestamp || new Date().toISOString()
|
|
662
|
-
};
|
|
663
|
-
|
|
664
|
-
this.processClaudeEvent(event);
|
|
665
|
-
|
|
666
|
-
// Only update if the File Tree tab is active
|
|
667
|
-
if (this.isTabActive()) {
|
|
668
|
-
this.buildTreeData();
|
|
669
|
-
this.renderTree();
|
|
670
|
-
this.updateStats();
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
/**
|
|
675
|
-
* Check if an event is a file operation (legacy format)
|
|
676
|
-
*/
|
|
677
|
-
isFileOperation(event) {
|
|
678
|
-
const fileOps = ['Read', 'Write', 'Edit', 'MultiEdit', 'NotebookEdit'];
|
|
679
|
-
return fileOps.includes(event.tool_name);
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
/**
|
|
683
|
-
* Process a claude event with file operation
|
|
684
|
-
*/
|
|
685
|
-
processClaudeEvent(event) {
|
|
686
|
-
// Handle both hook events and direct file events
|
|
687
|
-
if (!this.isFileOperationEvent(event) && !this.isDirectFileEvent(event)) return;
|
|
688
|
-
|
|
689
|
-
let tool_name, tool_parameters, tool_output, timestamp, session_id, working_directory, filePath;
|
|
690
|
-
|
|
691
|
-
// Extract data based on event format
|
|
692
|
-
if (this.isFileOperationEvent(event)) {
|
|
693
|
-
// Hook event format
|
|
694
|
-
const data = event.data || {};
|
|
695
|
-
tool_name = data.tool_name;
|
|
696
|
-
tool_parameters = data.tool_parameters || {};
|
|
697
|
-
tool_output = data.tool_output;
|
|
698
|
-
timestamp = event.timestamp || new Date().toISOString();
|
|
699
|
-
session_id = event.session_id || data.session_id;
|
|
700
|
-
working_directory = data.working_directory || '/';
|
|
701
|
-
} else if (this.isDirectFileEvent(event)) {
|
|
702
|
-
// Direct file event format
|
|
703
|
-
const data = event.data || event;
|
|
704
|
-
tool_name = event.tool || data.tool_name;
|
|
705
|
-
tool_parameters = data.tool_parameters || data.parameters || {};
|
|
706
|
-
tool_output = data.tool_output || data.output;
|
|
707
|
-
timestamp = event.timestamp || data.timestamp || new Date().toISOString();
|
|
708
|
-
session_id = event.session_id || data.session_id;
|
|
709
|
-
working_directory = data.working_directory || '/';
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
filePath = tool_parameters.file_path || tool_parameters.notebook_path;
|
|
713
|
-
|
|
714
|
-
this.processFileOperation({
|
|
715
|
-
tool_name,
|
|
716
|
-
tool_parameters,
|
|
717
|
-
tool_output,
|
|
718
|
-
timestamp,
|
|
719
|
-
session_id,
|
|
720
|
-
working_directory,
|
|
721
|
-
filePath
|
|
722
|
-
});
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
/**
|
|
726
|
-
* Process a file operation event (legacy format)
|
|
727
|
-
*/
|
|
728
|
-
processEvent(event) {
|
|
729
|
-
if (!this.isFileOperation(event)) return;
|
|
730
|
-
|
|
731
|
-
const { tool_name, tool_parameters, tool_output, timestamp, session_id, working_directory } = event;
|
|
732
|
-
const filePath = tool_parameters?.file_path || tool_parameters?.notebook_path;
|
|
733
|
-
|
|
734
|
-
this.processFileOperation({
|
|
735
|
-
tool_name,
|
|
736
|
-
tool_parameters,
|
|
737
|
-
tool_output,
|
|
738
|
-
timestamp,
|
|
739
|
-
session_id,
|
|
740
|
-
working_directory,
|
|
741
|
-
filePath
|
|
742
|
-
});
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
/**
|
|
746
|
-
* Process a file operation
|
|
747
|
-
*/
|
|
748
|
-
processFileOperation({ tool_name, tool_parameters, tool_output, timestamp, session_id, working_directory, filePath }) {
|
|
749
|
-
if (!filePath) return;
|
|
750
|
-
|
|
751
|
-
// Track session
|
|
752
|
-
if (session_id && !this.sessions.has(session_id)) {
|
|
753
|
-
this.sessions.set(session_id, {
|
|
754
|
-
id: session_id,
|
|
755
|
-
working_directory: working_directory || '/',
|
|
756
|
-
files: new Set()
|
|
757
|
-
});
|
|
758
|
-
// Update session list when new session is added
|
|
759
|
-
this.updateSessionList();
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
// Get or create file activity
|
|
763
|
-
if (!this.fileActivity.has(filePath)) {
|
|
764
|
-
this.fileActivity.set(filePath, {
|
|
765
|
-
path: filePath,
|
|
766
|
-
operations: [],
|
|
767
|
-
sessions: new Set(),
|
|
768
|
-
working_directories: new Set(),
|
|
769
|
-
lastContent: null,
|
|
770
|
-
astPaths: []
|
|
771
|
-
});
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
const activity = this.fileActivity.get(filePath);
|
|
775
|
-
|
|
776
|
-
// Add operation
|
|
777
|
-
activity.operations.push({
|
|
778
|
-
type: tool_name,
|
|
779
|
-
timestamp: timestamp,
|
|
780
|
-
parameters: tool_parameters,
|
|
781
|
-
output: tool_output,
|
|
782
|
-
session_id: session_id
|
|
783
|
-
});
|
|
784
|
-
|
|
785
|
-
// Track session and working directory
|
|
786
|
-
if (session_id) {
|
|
787
|
-
activity.sessions.add(session_id);
|
|
788
|
-
const session = this.sessions.get(session_id);
|
|
789
|
-
if (session) {
|
|
790
|
-
session.files.add(filePath);
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
if (working_directory) {
|
|
794
|
-
activity.working_directories.add(working_directory);
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
// Update content and extract AST if applicable
|
|
798
|
-
if (tool_name === 'Write' && tool_parameters.content) {
|
|
799
|
-
activity.lastContent = tool_parameters.content;
|
|
800
|
-
activity.astPaths = this.extractASTPaths(tool_parameters.content, filePath);
|
|
801
|
-
} else if (tool_name === 'Read' && tool_output?.content) {
|
|
802
|
-
activity.lastContent = tool_output.content;
|
|
803
|
-
activity.astPaths = this.extractASTPaths(tool_output.content, filePath);
|
|
804
|
-
} else if (tool_name === 'Edit' && activity.lastContent) {
|
|
805
|
-
// Apply edit to content if we have it
|
|
806
|
-
const oldString = tool_parameters.old_string;
|
|
807
|
-
const newString = tool_parameters.new_string;
|
|
808
|
-
if (oldString && newString) {
|
|
809
|
-
activity.lastContent = activity.lastContent.replace(oldString, newString);
|
|
810
|
-
activity.astPaths = this.extractASTPaths(activity.lastContent, filePath);
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
/**
|
|
816
|
-
* Extract AST paths from code content
|
|
817
|
-
*/
|
|
818
|
-
extractASTPaths(content, filePath) {
|
|
819
|
-
if (!content || typeof content !== 'string') return [];
|
|
820
|
-
|
|
821
|
-
const ext = filePath.split('.').pop()?.toLowerCase();
|
|
822
|
-
const paths = [];
|
|
823
|
-
|
|
824
|
-
if (ext === 'py') {
|
|
825
|
-
// Python: Extract classes, functions, and methods
|
|
826
|
-
const classRegex = /^class\s+(\w+)/gm;
|
|
827
|
-
const functionRegex = /^def\s+(\w+)/gm;
|
|
828
|
-
const methodRegex = /^\s{4,}def\s+(\w+)/gm;
|
|
829
|
-
|
|
830
|
-
let match;
|
|
831
|
-
while ((match = classRegex.exec(content)) !== null) {
|
|
832
|
-
paths.push({ name: match[1], type: 'class' });
|
|
833
|
-
}
|
|
834
|
-
while ((match = functionRegex.exec(content)) !== null) {
|
|
835
|
-
paths.push({ name: match[1], type: 'function' });
|
|
836
|
-
}
|
|
837
|
-
while ((match = methodRegex.exec(content)) !== null) {
|
|
838
|
-
if (!paths.some(p => p.name === match[1])) {
|
|
839
|
-
paths.push({ name: match[1], type: 'method' });
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
} else if (ext === 'js' || ext === 'jsx' || ext === 'ts' || ext === 'tsx') {
|
|
843
|
-
// JavaScript/TypeScript: Extract classes, functions, methods
|
|
844
|
-
const classRegex = /class\s+(\w+)/g;
|
|
845
|
-
const functionRegex = /function\s+(\w+)/g;
|
|
846
|
-
const arrowFunctionRegex = /const\s+(\w+)\s*=\s*\([^)]*\)\s*=>/g;
|
|
847
|
-
const methodRegex = /(\w+)\s*\([^)]*\)\s*\{/g;
|
|
848
|
-
|
|
849
|
-
let match;
|
|
850
|
-
while ((match = classRegex.exec(content)) !== null) {
|
|
851
|
-
paths.push({ name: match[1], type: 'class' });
|
|
852
|
-
}
|
|
853
|
-
while ((match = functionRegex.exec(content)) !== null) {
|
|
854
|
-
paths.push({ name: match[1], type: 'function' });
|
|
855
|
-
}
|
|
856
|
-
while ((match = arrowFunctionRegex.exec(content)) !== null) {
|
|
857
|
-
paths.push({ name: match[1], type: 'function' });
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
return paths;
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
/**
|
|
865
|
-
* Build tree data from file activity
|
|
866
|
-
*/
|
|
867
|
-
buildTreeData() {
|
|
868
|
-
// Get current session info
|
|
869
|
-
const sessionId = this.currentSession || 'current-session';
|
|
870
|
-
const sessionName = sessionId.substring(0, 8) + '...';
|
|
871
|
-
|
|
872
|
-
// Always create a root with at least the session node
|
|
873
|
-
const root = {
|
|
874
|
-
name: `Session: ${sessionName}`,
|
|
875
|
-
type: 'root',
|
|
876
|
-
children: []
|
|
877
|
-
};
|
|
878
|
-
|
|
879
|
-
// If no file activity, still show the session root
|
|
880
|
-
if (!this.fileActivity || this.fileActivity.size === 0) {
|
|
881
|
-
// Add a placeholder node to indicate no files yet
|
|
882
|
-
root.children.push({
|
|
883
|
-
name: '(No file operations yet)',
|
|
884
|
-
type: 'placeholder',
|
|
885
|
-
children: []
|
|
886
|
-
});
|
|
887
|
-
this.treeData = root;
|
|
888
|
-
console.log('[CodeViewer] Built minimal tree with session root');
|
|
889
|
-
return;
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
// Group by working directory
|
|
893
|
-
const dirMap = new Map();
|
|
894
|
-
|
|
895
|
-
for (const [filePath, activity] of this.fileActivity.entries()) {
|
|
896
|
-
// Filter by session if selected
|
|
897
|
-
if (this.currentSession) {
|
|
898
|
-
if (!activity.sessions.has(this.currentSession)) {
|
|
899
|
-
continue;
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
// Determine working directory
|
|
904
|
-
const workingDirs = Array.from(activity.working_directories);
|
|
905
|
-
const workingDir = workingDirs[0] || '/';
|
|
906
|
-
|
|
907
|
-
if (!dirMap.has(workingDir)) {
|
|
908
|
-
dirMap.set(workingDir, {
|
|
909
|
-
name: workingDir.split('/').pop() || workingDir,
|
|
910
|
-
path: workingDir,
|
|
911
|
-
type: 'directory',
|
|
912
|
-
children: []
|
|
913
|
-
});
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
// Create file node
|
|
917
|
-
const fileName = filePath.split('/').pop();
|
|
918
|
-
const hasEdits = activity.operations.some(op => op.type === 'Edit' || op.type === 'Write');
|
|
919
|
-
|
|
920
|
-
const fileNode = {
|
|
921
|
-
name: fileName,
|
|
922
|
-
path: filePath,
|
|
923
|
-
type: 'file',
|
|
924
|
-
edited: hasEdits,
|
|
925
|
-
operations: activity.operations.length,
|
|
926
|
-
children: []
|
|
927
|
-
};
|
|
928
|
-
|
|
929
|
-
// Add AST nodes
|
|
930
|
-
if (activity.astPaths.length > 0) {
|
|
931
|
-
activity.astPaths.forEach(ast => {
|
|
932
|
-
fileNode.children.push({
|
|
933
|
-
name: ast.name,
|
|
934
|
-
type: ast.type,
|
|
935
|
-
path: `${filePath}#${ast.name}`,
|
|
936
|
-
children: []
|
|
937
|
-
});
|
|
938
|
-
});
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
dirMap.get(workingDir).children.push(fileNode);
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
// Add directories to root
|
|
945
|
-
root.children = Array.from(dirMap.values());
|
|
946
|
-
|
|
947
|
-
// If only one directory and it's the root, flatten
|
|
948
|
-
if (root.children.length === 1 && root.children[0].path === '/') {
|
|
949
|
-
root.children = root.children[0].children;
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
this.treeData = root;
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
/**
|
|
956
|
-
* Render the D3 tree
|
|
957
|
-
*/
|
|
958
|
-
renderTree() {
|
|
959
|
-
if (!this.treeData || !this.container) return;
|
|
960
|
-
|
|
961
|
-
// Ensure SVG element exists
|
|
962
|
-
const svgElement = document.getElementById('claude-activity-tree-svg');
|
|
963
|
-
if (!svgElement) {
|
|
964
|
-
console.warn('[CodeViewer] SVG element not found, skipping tree render');
|
|
965
|
-
return;
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
const svg = d3.select(svgElement);
|
|
969
|
-
if (svg.empty()) {
|
|
970
|
-
console.warn('[CodeViewer] D3 could not select SVG element');
|
|
971
|
-
return;
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
svg.selectAll('*').remove();
|
|
975
|
-
|
|
976
|
-
// Get actual dimensions
|
|
977
|
-
const svgContainer = document.getElementById('claude-tree-svg-container');
|
|
978
|
-
if (svgContainer) {
|
|
979
|
-
const rect = svgContainer.getBoundingClientRect();
|
|
980
|
-
this.width = rect.width || 800;
|
|
981
|
-
this.height = rect.height || 600;
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
// Create container group for zoom/pan
|
|
985
|
-
const g = svg.append('g');
|
|
986
|
-
|
|
987
|
-
// Setup zoom behavior
|
|
988
|
-
const zoom = d3.zoom()
|
|
989
|
-
.scaleExtent([0.1, 4])
|
|
990
|
-
.on('zoom', (event) => {
|
|
991
|
-
g.attr('transform', event.transform);
|
|
992
|
-
});
|
|
993
|
-
|
|
994
|
-
svg.call(zoom);
|
|
995
|
-
|
|
996
|
-
// Create tree layout
|
|
997
|
-
const treeLayout = d3.tree()
|
|
998
|
-
.size([this.height - 100, this.width - 200]);
|
|
999
|
-
|
|
1000
|
-
// Create hierarchy
|
|
1001
|
-
this.d3Root = d3.hierarchy(this.treeData);
|
|
1002
|
-
|
|
1003
|
-
// Apply tree layout
|
|
1004
|
-
treeLayout(this.d3Root);
|
|
1005
|
-
|
|
1006
|
-
// Create links
|
|
1007
|
-
const link = g.selectAll('.link')
|
|
1008
|
-
.data(this.d3Root.links())
|
|
1009
|
-
.enter().append('path')
|
|
1010
|
-
.attr('class', 'link')
|
|
1011
|
-
.attr('d', d3.linkHorizontal()
|
|
1012
|
-
.x(d => d.y + 100)
|
|
1013
|
-
.y(d => d.x + 50))
|
|
1014
|
-
.style('fill', 'none')
|
|
1015
|
-
.style('stroke', '#ccc')
|
|
1016
|
-
.style('stroke-width', 1);
|
|
1017
|
-
|
|
1018
|
-
// Create nodes
|
|
1019
|
-
const node = g.selectAll('.node')
|
|
1020
|
-
.data(this.d3Root.descendants())
|
|
1021
|
-
.enter().append('g')
|
|
1022
|
-
.attr('class', 'node')
|
|
1023
|
-
.attr('transform', d => `translate(${d.y + 100},${d.x + 50})`);
|
|
1024
|
-
|
|
1025
|
-
// Add circles for nodes
|
|
1026
|
-
node.append('circle')
|
|
1027
|
-
.attr('r', this.nodeRadius)
|
|
1028
|
-
.style('fill', d => this.getNodeColor(d.data))
|
|
1029
|
-
.style('stroke', d => d.data.edited ? '#F44336' : '#999')
|
|
1030
|
-
.style('stroke-width', d => d.data.edited ? 2 : 1)
|
|
1031
|
-
.style('cursor', 'pointer')
|
|
1032
|
-
.on('click', (event, d) => this.handleNodeClick(event, d));
|
|
1033
|
-
|
|
1034
|
-
// Add text labels
|
|
1035
|
-
node.append('text')
|
|
1036
|
-
.attr('dy', '.35em')
|
|
1037
|
-
.attr('x', d => d.children ? -10 : 10)
|
|
1038
|
-
.style('text-anchor', d => d.children ? 'end' : 'start')
|
|
1039
|
-
.style('font-size', '12px')
|
|
1040
|
-
.style('cursor', 'pointer')
|
|
1041
|
-
.text(d => d.data.name)
|
|
1042
|
-
.on('click', (event, d) => this.handleNodeClick(event, d));
|
|
1043
|
-
|
|
1044
|
-
// Store tree reference
|
|
1045
|
-
this.d3Tree = { svg, g, zoom };
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
/**
|
|
1049
|
-
* Get node color based on type
|
|
1050
|
-
*/
|
|
1051
|
-
getNodeColor(node) {
|
|
1052
|
-
switch (node.type) {
|
|
1053
|
-
case 'root': return '#666';
|
|
1054
|
-
case 'directory': return '#FFC107';
|
|
1055
|
-
case 'file': return '#4CAF50';
|
|
1056
|
-
case 'class': return '#2196F3';
|
|
1057
|
-
case 'function': return '#FF9800';
|
|
1058
|
-
case 'method': return '#9C27B0';
|
|
1059
|
-
default: return '#999';
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
/**
|
|
1064
|
-
* Handle node click
|
|
1065
|
-
*/
|
|
1066
|
-
handleNodeClick(event, d) {
|
|
1067
|
-
event.stopPropagation();
|
|
1068
|
-
|
|
1069
|
-
// Toggle children
|
|
1070
|
-
if (d.children) {
|
|
1071
|
-
d._children = d.children;
|
|
1072
|
-
d.children = null;
|
|
1073
|
-
} else if (d._children) {
|
|
1074
|
-
d.children = d._children;
|
|
1075
|
-
d._children = null;
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
// Re-render tree
|
|
1079
|
-
this.renderTree();
|
|
1080
|
-
|
|
1081
|
-
// Update selection
|
|
1082
|
-
this.selectedNode = d;
|
|
1083
|
-
|
|
1084
|
-
// Update the data viewer in the left pane if it's a file
|
|
1085
|
-
if (d.data.type === 'file' && this.fileActivity.has(d.data.path)) {
|
|
1086
|
-
this.showFileDetails(d.data.path);
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
/**
|
|
1091
|
-
* Show file details in the left viewer pane
|
|
1092
|
-
*/
|
|
1093
|
-
showFileDetails(filePath) {
|
|
1094
|
-
const activity = this.fileActivity.get(filePath);
|
|
1095
|
-
if (!activity) return;
|
|
1096
|
-
|
|
1097
|
-
const dataContent = document.getElementById('module-data-content');
|
|
1098
|
-
if (!dataContent) return;
|
|
1099
|
-
|
|
1100
|
-
// Update header
|
|
1101
|
-
const dataHeader = document.querySelector('.module-data-header h5');
|
|
1102
|
-
if (dataHeader) {
|
|
1103
|
-
dataHeader.innerHTML = `📄 ${filePath.split('/').pop()}`;
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
// Build operations display
|
|
1107
|
-
let html = '<div style="padding: 10px; overflow-y: auto; height: 100%;">';
|
|
1108
|
-
html += `<div style="margin-bottom: 15px;">`;
|
|
1109
|
-
html += `<strong>File Path:</strong> ${filePath}<br>`;
|
|
1110
|
-
html += `<strong>Operations:</strong> ${activity.operations.length}<br>`;
|
|
1111
|
-
html += `<strong>Sessions:</strong> ${activity.sessions.size}`;
|
|
1112
|
-
html += `</div>`;
|
|
1113
|
-
|
|
1114
|
-
// Show operations timeline
|
|
1115
|
-
html += '<div style="margin-bottom: 15px;"><strong>Operations Timeline:</strong></div>';
|
|
1116
|
-
activity.operations.forEach((op, index) => {
|
|
1117
|
-
const time = new Date(op.timestamp).toLocaleTimeString();
|
|
1118
|
-
html += `<div style="margin-bottom: 10px; padding: 8px; background: #f5f5f5; border-left: 3px solid ${this.getOperationColor(op.type)};">`;
|
|
1119
|
-
html += `<div><strong>${op.type}</strong> at ${time}</div>`;
|
|
1120
|
-
|
|
1121
|
-
if (op.type === 'Edit' && op.parameters) {
|
|
1122
|
-
html += `<div style="margin-top: 5px; font-size: 0.9em;">`;
|
|
1123
|
-
html += `<div style="color: #d32f2f;">- ${this.escapeHtml(op.parameters.old_string || '').substring(0, 100)}</div>`;
|
|
1124
|
-
html += `<div style="color: #388e3c;">+ ${this.escapeHtml(op.parameters.new_string || '').substring(0, 100)}</div>`;
|
|
1125
|
-
html += `</div>`;
|
|
1126
|
-
}
|
|
1127
|
-
html += `</div>`;
|
|
1128
|
-
});
|
|
1129
|
-
|
|
1130
|
-
// Show AST structure if available
|
|
1131
|
-
if (activity.astPaths.length > 0) {
|
|
1132
|
-
html += '<div style="margin-top: 15px;"><strong>AST Structure:</strong></div>';
|
|
1133
|
-
html += '<ul style="list-style: none; padding-left: 10px;">';
|
|
1134
|
-
activity.astPaths.forEach(ast => {
|
|
1135
|
-
const icon = ast.type === 'class' ? '🔷' : ast.type === 'function' ? '🔶' : '🔸';
|
|
1136
|
-
html += `<li>${icon} ${ast.name} (${ast.type})</li>`;
|
|
1137
|
-
});
|
|
1138
|
-
html += '</ul>';
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
html += '</div>';
|
|
1142
|
-
dataContent.innerHTML = html;
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
/**
|
|
1146
|
-
* Get operation color
|
|
1147
|
-
*/
|
|
1148
|
-
getOperationColor(type) {
|
|
1149
|
-
switch (type) {
|
|
1150
|
-
case 'Write': return '#4CAF50';
|
|
1151
|
-
case 'Edit': return '#FF9800';
|
|
1152
|
-
case 'Read': return '#2196F3';
|
|
1153
|
-
default: return '#999';
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
/**
|
|
1158
|
-
* Escape HTML
|
|
1159
|
-
*/
|
|
1160
|
-
escapeHtml(text) {
|
|
1161
|
-
const div = document.createElement('div');
|
|
1162
|
-
div.textContent = text;
|
|
1163
|
-
return div.innerHTML;
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
/**
|
|
1167
|
-
* Expand all nodes
|
|
1168
|
-
*/
|
|
1169
|
-
expandAllNodes() {
|
|
1170
|
-
if (!this.d3Root) return;
|
|
1171
|
-
|
|
1172
|
-
this.d3Root.descendants().forEach(d => {
|
|
1173
|
-
if (d._children) {
|
|
1174
|
-
d.children = d._children;
|
|
1175
|
-
d._children = null;
|
|
1176
|
-
}
|
|
1177
|
-
});
|
|
1178
|
-
|
|
1179
|
-
this.renderTree();
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
/**
|
|
1183
|
-
* Collapse all nodes
|
|
1184
|
-
*/
|
|
1185
|
-
collapseAllNodes() {
|
|
1186
|
-
if (!this.d3Root) return;
|
|
1187
|
-
|
|
1188
|
-
this.d3Root.descendants().forEach(d => {
|
|
1189
|
-
if (d.children && d.depth > 0) {
|
|
1190
|
-
d._children = d.children;
|
|
1191
|
-
d.children = null;
|
|
1192
|
-
}
|
|
1193
|
-
});
|
|
1194
|
-
|
|
1195
|
-
this.renderTree();
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
/**
|
|
1199
|
-
* Reset zoom
|
|
1200
|
-
*/
|
|
1201
|
-
resetZoom() {
|
|
1202
|
-
if (!this.d3Tree) return;
|
|
1203
|
-
|
|
1204
|
-
this.d3Tree.svg.transition()
|
|
1205
|
-
.duration(750)
|
|
1206
|
-
.call(this.d3Tree.zoom.transform, d3.zoomIdentity);
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
/**
|
|
1210
|
-
* Update session list in main selector
|
|
1211
|
-
*/
|
|
1212
|
-
updateSessionList() {
|
|
1213
|
-
// Update the main session selector if it exists
|
|
1214
|
-
const mainSelect = document.getElementById('session-select');
|
|
1215
|
-
if (!mainSelect) return;
|
|
1216
|
-
|
|
1217
|
-
const currentValue = mainSelect.value;
|
|
1218
|
-
|
|
1219
|
-
// Clear existing options except "All Sessions"
|
|
1220
|
-
while (mainSelect.options.length > 1) {
|
|
1221
|
-
mainSelect.remove(1);
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
// Add session options from our tracked sessions
|
|
1225
|
-
for (const [sessionId, session] of this.sessions.entries()) {
|
|
1226
|
-
// Check if option already exists
|
|
1227
|
-
let exists = false;
|
|
1228
|
-
for (let i = 0; i < mainSelect.options.length; i++) {
|
|
1229
|
-
if (mainSelect.options[i].value === sessionId) {
|
|
1230
|
-
exists = true;
|
|
1231
|
-
break;
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
if (!exists) {
|
|
1236
|
-
const option = document.createElement('option');
|
|
1237
|
-
option.value = sessionId;
|
|
1238
|
-
option.textContent = `Session ${sessionId.substring(0, 8)}... (${session.files.size} files)`;
|
|
1239
|
-
mainSelect.appendChild(option);
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
// Restore previous selection if it still exists
|
|
1244
|
-
if (currentValue) {
|
|
1245
|
-
mainSelect.value = currentValue;
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
/**
|
|
1250
|
-
* Update statistics
|
|
1251
|
-
*/
|
|
1252
|
-
updateStats() {
|
|
1253
|
-
const stats = document.getElementById('claude-tree-stats');
|
|
1254
|
-
if (!stats) return;
|
|
1255
|
-
|
|
1256
|
-
const totalFiles = this.currentSession
|
|
1257
|
-
? Array.from(this.fileActivity.values()).filter(a => a.sessions.has(this.currentSession)).length
|
|
1258
|
-
: this.fileActivity.size;
|
|
1259
|
-
|
|
1260
|
-
const totalOps = this.currentSession
|
|
1261
|
-
? Array.from(this.fileActivity.values())
|
|
1262
|
-
.filter(a => a.sessions.has(this.currentSession))
|
|
1263
|
-
.reduce((sum, a) => sum + a.operations.length, 0)
|
|
1264
|
-
: Array.from(this.fileActivity.values())
|
|
1265
|
-
.reduce((sum, a) => sum + a.operations.length, 0);
|
|
1266
|
-
|
|
1267
|
-
stats.textContent = `Files: ${totalFiles} | Operations: ${totalOps} | Sessions: ${this.sessions.size}`;
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
/**
|
|
1271
|
-
* Refresh file activity from FileToolTracker
|
|
1272
|
-
*/
|
|
1273
|
-
refreshFromFileToolTracker() {
|
|
1274
|
-
if (!window.dashboard || !window.dashboard.fileToolTracker) {
|
|
1275
|
-
console.log('[CodeViewer] FileToolTracker not available');
|
|
1276
|
-
return;
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
const fileOperations = window.dashboard.fileToolTracker.getFileOperations();
|
|
1280
|
-
console.log('[CodeViewer] Refreshing from FileToolTracker:', fileOperations.size, 'files');
|
|
1281
|
-
|
|
1282
|
-
// Clear existing file activity
|
|
1283
|
-
this.fileActivity.clear();
|
|
1284
|
-
this.sessions.clear();
|
|
1285
|
-
|
|
1286
|
-
// Create a default session if needed
|
|
1287
|
-
const defaultSessionId = 'current-session';
|
|
1288
|
-
this.sessions.set(defaultSessionId, {
|
|
1289
|
-
id: defaultSessionId,
|
|
1290
|
-
startTime: new Date().toISOString(),
|
|
1291
|
-
files: new Set()
|
|
1292
|
-
});
|
|
1293
|
-
this.currentSession = defaultSessionId;
|
|
1294
|
-
|
|
1295
|
-
// Convert fileOperations Map to file activity for tree visualization
|
|
1296
|
-
fileOperations.forEach((fileData, filePath) => {
|
|
1297
|
-
// Add file to session
|
|
1298
|
-
const session = this.sessions.get(defaultSessionId);
|
|
1299
|
-
session.files.add(filePath);
|
|
1300
|
-
|
|
1301
|
-
// Create file activity entry
|
|
1302
|
-
const firstOp = fileData.operations[0];
|
|
1303
|
-
const lastOp = fileData.operations[fileData.operations.length - 1];
|
|
1304
|
-
|
|
1305
|
-
this.fileActivity.set(filePath, {
|
|
1306
|
-
path: filePath,
|
|
1307
|
-
firstAccess: firstOp ? firstOp.timestamp : fileData.lastOperation,
|
|
1308
|
-
lastAccess: fileData.lastOperation,
|
|
1309
|
-
accessCount: fileData.operations.length,
|
|
1310
|
-
operations: fileData.operations.map(op => ({
|
|
1311
|
-
type: op.operation,
|
|
1312
|
-
timestamp: op.timestamp,
|
|
1313
|
-
agent: op.agent
|
|
1314
|
-
})),
|
|
1315
|
-
workingDirectory: firstOp ? firstOp.workingDirectory : null,
|
|
1316
|
-
astNodes: [],
|
|
1317
|
-
content: null
|
|
1318
|
-
});
|
|
1319
|
-
});
|
|
1320
|
-
|
|
1321
|
-
console.log('[CodeViewer] File activity refreshed:', this.fileActivity.size, 'files');
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
// Create and export singleton instance
|
|
1326
|
-
try {
|
|
1327
|
-
window.CodeViewer = new CodeViewer();
|
|
1328
|
-
console.log('[CodeViewer] Instance created successfully');
|
|
1329
|
-
} catch (error) {
|
|
1330
|
-
console.error('[CodeViewer] FAILED TO CREATE INSTANCE:', error);
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
// Auto-initialize when DOM is ready
|
|
1334
|
-
if (document.readyState === 'loading') {
|
|
1335
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
1336
|
-
console.log('[CodeViewer] DOMContentLoaded - attempting initialization');
|
|
1337
|
-
try {
|
|
1338
|
-
window.CodeViewer.initialize();
|
|
1339
|
-
|
|
1340
|
-
// If File Tree tab is already active, show it
|
|
1341
|
-
const claudeTreeTab = document.getElementById('claude-tree-tab');
|
|
1342
|
-
if (claudeTreeTab && claudeTreeTab.classList.contains('active')) {
|
|
1343
|
-
setTimeout(() => window.CodeViewer.show(), 100);
|
|
1344
|
-
}
|
|
1345
|
-
} catch (error) {
|
|
1346
|
-
console.error('[CodeViewer] FAILED TO INITIALIZE:', error);
|
|
1347
|
-
}
|
|
1348
|
-
});
|
|
1349
|
-
} else {
|
|
1350
|
-
console.log('[CodeViewer] DOM already loaded - initializing immediately');
|
|
1351
|
-
try {
|
|
1352
|
-
window.CodeViewer.initialize();
|
|
1353
|
-
|
|
1354
|
-
// If File Tree tab is already active, show it
|
|
1355
|
-
const claudeTreeTab = document.getElementById('claude-tree-tab');
|
|
1356
|
-
if (claudeTreeTab && claudeTreeTab.classList.contains('active')) {
|
|
1357
|
-
console.log('[CodeViewer] File Tree tab is active, showing in 100ms');
|
|
1358
|
-
setTimeout(() => window.CodeViewer.show(), 100);
|
|
1359
|
-
}
|
|
1360
|
-
} catch (error) {
|
|
1361
|
-
console.error('[CodeViewer] FAILED TO INITIALIZE:', error);
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
// Also listen for tab changes to ensure we render when needed
|
|
1366
|
-
document.addEventListener('tabChanged', (event) => {
|
|
1367
|
-
if (event.detail && event.detail.newTab === 'claude-tree') {
|
|
1368
|
-
setTimeout(() => window.CodeViewer.show(), 50);
|
|
1369
|
-
}
|
|
1370
|
-
});
|
|
1371
|
-
|
|
1372
|
-
// Tab click handling is now done by UIStateManager
|
|
1373
|
-
// CodeViewer.renderContent() is called when the File Tree tab is activated
|
|
1374
|
-
|
|
1375
|
-
// FALLBACK: Periodic check to ensure File Tree tab is properly rendered
|
|
1376
|
-
setInterval(() => {
|
|
1377
|
-
const claudeTreeTab = document.getElementById('claude-tree-tab');
|
|
1378
|
-
const claudeTreeContainer = document.getElementById('claude-tree-container');
|
|
1379
|
-
|
|
1380
|
-
if (claudeTreeTab && claudeTreeTab.classList.contains('active') &&
|
|
1381
|
-
claudeTreeContainer &&
|
|
1382
|
-
!claudeTreeContainer.querySelector('.activity-tree-wrapper') &&
|
|
1383
|
-
!claudeTreeContainer.querySelector('.file-tree-empty-state')) {
|
|
1384
|
-
window.CodeViewer.show();
|
|
1385
|
-
}
|
|
1386
|
-
}, 5000);
|