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.
Files changed (250) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_ENGINEER.md +286 -0
  3. claude_mpm/agents/BASE_PM.md +48 -17
  4. claude_mpm/agents/OUTPUT_STYLE.md +329 -11
  5. claude_mpm/agents/PM_INSTRUCTIONS.md +227 -8
  6. claude_mpm/agents/agent_loader.py +17 -5
  7. claude_mpm/agents/frontmatter_validator.py +284 -253
  8. claude_mpm/agents/templates/agentic-coder-optimizer.json +9 -2
  9. claude_mpm/agents/templates/api_qa.json +7 -1
  10. claude_mpm/agents/templates/clerk-ops.json +8 -1
  11. claude_mpm/agents/templates/code_analyzer.json +4 -1
  12. claude_mpm/agents/templates/dart_engineer.json +11 -1
  13. claude_mpm/agents/templates/data_engineer.json +11 -1
  14. claude_mpm/agents/templates/documentation.json +6 -1
  15. claude_mpm/agents/templates/engineer.json +18 -1
  16. claude_mpm/agents/templates/gcp_ops_agent.json +8 -1
  17. claude_mpm/agents/templates/golang_engineer.json +11 -1
  18. claude_mpm/agents/templates/java_engineer.json +12 -2
  19. claude_mpm/agents/templates/local_ops_agent.json +1217 -6
  20. claude_mpm/agents/templates/nextjs_engineer.json +11 -1
  21. claude_mpm/agents/templates/ops.json +8 -1
  22. claude_mpm/agents/templates/php-engineer.json +11 -1
  23. claude_mpm/agents/templates/project_organizer.json +10 -3
  24. claude_mpm/agents/templates/prompt-engineer.json +5 -1
  25. claude_mpm/agents/templates/python_engineer.json +11 -1
  26. claude_mpm/agents/templates/qa.json +7 -1
  27. claude_mpm/agents/templates/react_engineer.json +11 -1
  28. claude_mpm/agents/templates/refactoring_engineer.json +8 -1
  29. claude_mpm/agents/templates/research.json +4 -1
  30. claude_mpm/agents/templates/ruby-engineer.json +11 -1
  31. claude_mpm/agents/templates/rust_engineer.json +11 -1
  32. claude_mpm/agents/templates/security.json +6 -1
  33. claude_mpm/agents/templates/svelte-engineer.json +225 -0
  34. claude_mpm/agents/templates/ticketing.json +6 -1
  35. claude_mpm/agents/templates/typescript_engineer.json +11 -1
  36. claude_mpm/agents/templates/vercel_ops_agent.json +8 -1
  37. claude_mpm/agents/templates/version_control.json +8 -1
  38. claude_mpm/agents/templates/web_qa.json +7 -1
  39. claude_mpm/agents/templates/web_ui.json +11 -1
  40. claude_mpm/cli/__init__.py +34 -706
  41. claude_mpm/cli/commands/agent_manager.py +25 -12
  42. claude_mpm/cli/commands/agent_state_manager.py +186 -0
  43. claude_mpm/cli/commands/agents.py +204 -148
  44. claude_mpm/cli/commands/aggregate.py +7 -3
  45. claude_mpm/cli/commands/analyze.py +9 -4
  46. claude_mpm/cli/commands/analyze_code.py +7 -2
  47. claude_mpm/cli/commands/auto_configure.py +7 -9
  48. claude_mpm/cli/commands/config.py +47 -13
  49. claude_mpm/cli/commands/configure.py +294 -1788
  50. claude_mpm/cli/commands/configure_agent_display.py +261 -0
  51. claude_mpm/cli/commands/configure_behavior_manager.py +204 -0
  52. claude_mpm/cli/commands/configure_hook_manager.py +225 -0
  53. claude_mpm/cli/commands/configure_models.py +18 -0
  54. claude_mpm/cli/commands/configure_navigation.py +167 -0
  55. claude_mpm/cli/commands/configure_paths.py +104 -0
  56. claude_mpm/cli/commands/configure_persistence.py +254 -0
  57. claude_mpm/cli/commands/configure_startup_manager.py +646 -0
  58. claude_mpm/cli/commands/configure_template_editor.py +497 -0
  59. claude_mpm/cli/commands/configure_validators.py +73 -0
  60. claude_mpm/cli/commands/local_deploy.py +537 -0
  61. claude_mpm/cli/commands/memory.py +54 -20
  62. claude_mpm/cli/commands/mpm_init.py +39 -25
  63. claude_mpm/cli/commands/mpm_init_handler.py +8 -3
  64. claude_mpm/cli/executor.py +202 -0
  65. claude_mpm/cli/helpers.py +105 -0
  66. claude_mpm/cli/interactive/__init__.py +3 -0
  67. claude_mpm/cli/interactive/skills_wizard.py +491 -0
  68. claude_mpm/cli/parsers/__init__.py +7 -1
  69. claude_mpm/cli/parsers/base_parser.py +98 -3
  70. claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
  71. claude_mpm/cli/shared/output_formatters.py +28 -19
  72. claude_mpm/cli/startup.py +481 -0
  73. claude_mpm/cli/utils.py +52 -1
  74. claude_mpm/commands/mpm-help.md +3 -0
  75. claude_mpm/commands/mpm-version.md +113 -0
  76. claude_mpm/commands/mpm.md +1 -0
  77. claude_mpm/config/agent_config.py +2 -2
  78. claude_mpm/config/model_config.py +428 -0
  79. claude_mpm/core/base_service.py +13 -12
  80. claude_mpm/core/enums.py +452 -0
  81. claude_mpm/core/factories.py +1 -1
  82. claude_mpm/core/instruction_reinforcement_hook.py +2 -1
  83. claude_mpm/core/interactive_session.py +9 -3
  84. claude_mpm/core/logging_config.py +6 -2
  85. claude_mpm/core/oneshot_session.py +8 -4
  86. claude_mpm/core/optimized_agent_loader.py +3 -3
  87. claude_mpm/core/output_style_manager.py +12 -192
  88. claude_mpm/core/service_registry.py +5 -1
  89. claude_mpm/core/types.py +2 -9
  90. claude_mpm/core/typing_utils.py +7 -6
  91. claude_mpm/dashboard/static/js/dashboard.js +0 -14
  92. claude_mpm/dashboard/templates/index.html +3 -41
  93. claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
  94. claude_mpm/hooks/instruction_reinforcement.py +7 -2
  95. claude_mpm/models/resume_log.py +340 -0
  96. claude_mpm/services/agents/auto_config_manager.py +10 -11
  97. claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
  98. claude_mpm/services/agents/deployment/agent_record_service.py +1 -1
  99. claude_mpm/services/agents/deployment/agent_validator.py +17 -1
  100. claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
  101. claude_mpm/services/agents/deployment/interface_adapter.py +3 -2
  102. claude_mpm/services/agents/deployment/local_template_deployment.py +1 -1
  103. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +7 -6
  104. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +7 -16
  105. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +4 -3
  106. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +5 -3
  107. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +6 -5
  108. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +9 -6
  109. claude_mpm/services/agents/deployment/validation/__init__.py +3 -1
  110. claude_mpm/services/agents/deployment/validation/validation_result.py +1 -9
  111. claude_mpm/services/agents/local_template_manager.py +1 -1
  112. claude_mpm/services/agents/memory/agent_memory_manager.py +5 -2
  113. claude_mpm/services/agents/registry/modification_tracker.py +5 -2
  114. claude_mpm/services/command_handler_service.py +11 -5
  115. claude_mpm/services/core/interfaces/__init__.py +74 -2
  116. claude_mpm/services/core/interfaces/health.py +172 -0
  117. claude_mpm/services/core/interfaces/model.py +281 -0
  118. claude_mpm/services/core/interfaces/process.py +372 -0
  119. claude_mpm/services/core/interfaces/restart.py +307 -0
  120. claude_mpm/services/core/interfaces/stability.py +260 -0
  121. claude_mpm/services/core/models/__init__.py +33 -0
  122. claude_mpm/services/core/models/agent_config.py +12 -28
  123. claude_mpm/services/core/models/health.py +162 -0
  124. claude_mpm/services/core/models/process.py +235 -0
  125. claude_mpm/services/core/models/restart.py +302 -0
  126. claude_mpm/services/core/models/stability.py +264 -0
  127. claude_mpm/services/core/path_resolver.py +23 -7
  128. claude_mpm/services/diagnostics/__init__.py +2 -2
  129. claude_mpm/services/diagnostics/checks/agent_check.py +25 -24
  130. claude_mpm/services/diagnostics/checks/claude_code_check.py +24 -23
  131. claude_mpm/services/diagnostics/checks/common_issues_check.py +25 -24
  132. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -23
  133. claude_mpm/services/diagnostics/checks/filesystem_check.py +18 -17
  134. claude_mpm/services/diagnostics/checks/installation_check.py +30 -29
  135. claude_mpm/services/diagnostics/checks/instructions_check.py +20 -19
  136. claude_mpm/services/diagnostics/checks/mcp_check.py +50 -36
  137. claude_mpm/services/diagnostics/checks/mcp_services_check.py +36 -31
  138. claude_mpm/services/diagnostics/checks/monitor_check.py +23 -22
  139. claude_mpm/services/diagnostics/checks/startup_log_check.py +9 -8
  140. claude_mpm/services/diagnostics/diagnostic_runner.py +6 -5
  141. claude_mpm/services/diagnostics/doctor_reporter.py +28 -25
  142. claude_mpm/services/diagnostics/models.py +19 -24
  143. claude_mpm/services/infrastructure/monitoring/__init__.py +1 -1
  144. claude_mpm/services/infrastructure/monitoring/aggregator.py +12 -12
  145. claude_mpm/services/infrastructure/monitoring/base.py +5 -13
  146. claude_mpm/services/infrastructure/monitoring/network.py +7 -6
  147. claude_mpm/services/infrastructure/monitoring/process.py +13 -12
  148. claude_mpm/services/infrastructure/monitoring/resources.py +7 -6
  149. claude_mpm/services/infrastructure/monitoring/service.py +16 -15
  150. claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
  151. claude_mpm/services/local_ops/__init__.py +163 -0
  152. claude_mpm/services/local_ops/crash_detector.py +257 -0
  153. claude_mpm/services/local_ops/health_checks/__init__.py +28 -0
  154. claude_mpm/services/local_ops/health_checks/http_check.py +224 -0
  155. claude_mpm/services/local_ops/health_checks/process_check.py +236 -0
  156. claude_mpm/services/local_ops/health_checks/resource_check.py +255 -0
  157. claude_mpm/services/local_ops/health_manager.py +430 -0
  158. claude_mpm/services/local_ops/log_monitor.py +396 -0
  159. claude_mpm/services/local_ops/memory_leak_detector.py +294 -0
  160. claude_mpm/services/local_ops/process_manager.py +595 -0
  161. claude_mpm/services/local_ops/resource_monitor.py +331 -0
  162. claude_mpm/services/local_ops/restart_manager.py +401 -0
  163. claude_mpm/services/local_ops/restart_policy.py +387 -0
  164. claude_mpm/services/local_ops/state_manager.py +372 -0
  165. claude_mpm/services/local_ops/unified_manager.py +600 -0
  166. claude_mpm/services/mcp_config_manager.py +9 -4
  167. claude_mpm/services/mcp_gateway/core/__init__.py +1 -2
  168. claude_mpm/services/mcp_gateway/core/base.py +18 -31
  169. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +71 -24
  170. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +30 -28
  171. claude_mpm/services/memory_hook_service.py +4 -1
  172. claude_mpm/services/model/__init__.py +147 -0
  173. claude_mpm/services/model/base_provider.py +365 -0
  174. claude_mpm/services/model/claude_provider.py +412 -0
  175. claude_mpm/services/model/model_router.py +453 -0
  176. claude_mpm/services/model/ollama_provider.py +415 -0
  177. claude_mpm/services/monitor/daemon_manager.py +3 -2
  178. claude_mpm/services/monitor/handlers/dashboard.py +2 -1
  179. claude_mpm/services/monitor/handlers/hooks.py +2 -1
  180. claude_mpm/services/monitor/management/lifecycle.py +3 -2
  181. claude_mpm/services/monitor/server.py +2 -1
  182. claude_mpm/services/session_management_service.py +3 -2
  183. claude_mpm/services/session_manager.py +205 -1
  184. claude_mpm/services/shared/async_service_base.py +16 -27
  185. claude_mpm/services/shared/lifecycle_service_base.py +1 -14
  186. claude_mpm/services/socketio/handlers/__init__.py +5 -2
  187. claude_mpm/services/socketio/handlers/hook.py +13 -2
  188. claude_mpm/services/socketio/handlers/registry.py +4 -2
  189. claude_mpm/services/socketio/server/main.py +10 -8
  190. claude_mpm/services/subprocess_launcher_service.py +14 -5
  191. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +8 -7
  192. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +6 -5
  193. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +8 -7
  194. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +7 -6
  195. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +5 -4
  196. claude_mpm/services/unified/config_strategies/validation_strategy.py +13 -9
  197. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +10 -3
  198. claude_mpm/services/unified/deployment_strategies/local.py +6 -5
  199. claude_mpm/services/unified/deployment_strategies/utils.py +6 -5
  200. claude_mpm/services/unified/deployment_strategies/vercel.py +7 -6
  201. claude_mpm/services/unified/interfaces.py +3 -1
  202. claude_mpm/services/unified/unified_analyzer.py +14 -10
  203. claude_mpm/services/unified/unified_config.py +2 -1
  204. claude_mpm/services/unified/unified_deployment.py +9 -4
  205. claude_mpm/services/version_service.py +104 -1
  206. claude_mpm/skills/__init__.py +21 -0
  207. claude_mpm/skills/bundled/__init__.py +6 -0
  208. claude_mpm/skills/bundled/api-documentation.md +393 -0
  209. claude_mpm/skills/bundled/async-testing.md +571 -0
  210. claude_mpm/skills/bundled/code-review.md +143 -0
  211. claude_mpm/skills/bundled/database-migration.md +199 -0
  212. claude_mpm/skills/bundled/docker-containerization.md +194 -0
  213. claude_mpm/skills/bundled/express-local-dev.md +1429 -0
  214. claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
  215. claude_mpm/skills/bundled/git-workflow.md +414 -0
  216. claude_mpm/skills/bundled/imagemagick.md +204 -0
  217. claude_mpm/skills/bundled/json-data-handling.md +223 -0
  218. claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
  219. claude_mpm/skills/bundled/pdf.md +141 -0
  220. claude_mpm/skills/bundled/performance-profiling.md +567 -0
  221. claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
  222. claude_mpm/skills/bundled/security-scanning.md +327 -0
  223. claude_mpm/skills/bundled/systematic-debugging.md +473 -0
  224. claude_mpm/skills/bundled/test-driven-development.md +378 -0
  225. claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
  226. claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
  227. claude_mpm/skills/bundled/xlsx.md +157 -0
  228. claude_mpm/skills/registry.py +286 -0
  229. claude_mpm/skills/skill_manager.py +310 -0
  230. claude_mpm/tools/code_tree_analyzer.py +177 -141
  231. claude_mpm/tools/code_tree_events.py +4 -2
  232. claude_mpm/utils/agent_dependency_loader.py +2 -2
  233. {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/METADATA +117 -8
  234. {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/RECORD +238 -174
  235. claude_mpm/dashboard/static/css/code-tree.css +0 -1639
  236. claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +0 -353
  237. claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +0 -235
  238. claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +0 -409
  239. claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +0 -435
  240. claude_mpm/dashboard/static/js/components/code-tree.js +0 -5869
  241. claude_mpm/dashboard/static/js/components/code-viewer.js +0 -1386
  242. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +0 -425
  243. claude_mpm/hooks/claude_hooks/hook_handler_original.py +0 -1041
  244. claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +0 -347
  245. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +0 -575
  246. claude_mpm/services/project/analyzer_refactored.py +0 -450
  247. {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/WHEEL +0 -0
  248. {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/entry_points.txt +0 -0
  249. {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/licenses/LICENSE +0 -0
  250. {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);