claude-mpm 4.2.9__py3-none-any.whl → 4.2.11__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 (50) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/cli/commands/dashboard.py +59 -126
  3. claude_mpm/cli/commands/monitor.py +71 -212
  4. claude_mpm/cli/commands/run.py +33 -33
  5. claude_mpm/dashboard/static/css/code-tree.css +8 -16
  6. claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
  7. claude_mpm/dashboard/static/dist/components/file-viewer.js +2 -0
  8. claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
  9. claude_mpm/dashboard/static/dist/components/unified-data-viewer.js +1 -1
  10. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  11. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  12. claude_mpm/dashboard/static/js/components/code-tree.js +692 -114
  13. claude_mpm/dashboard/static/js/components/file-viewer.js +538 -0
  14. claude_mpm/dashboard/static/js/components/module-viewer.js +26 -0
  15. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +166 -14
  16. claude_mpm/dashboard/static/js/dashboard.js +108 -91
  17. claude_mpm/dashboard/static/js/socket-client.js +9 -7
  18. claude_mpm/dashboard/templates/index.html +2 -7
  19. claude_mpm/hooks/claude_hooks/hook_handler.py +1 -11
  20. claude_mpm/hooks/claude_hooks/services/connection_manager.py +54 -59
  21. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +112 -72
  22. claude_mpm/services/agents/deployment/agent_template_builder.py +0 -1
  23. claude_mpm/services/cli/unified_dashboard_manager.py +354 -0
  24. claude_mpm/services/monitor/__init__.py +20 -0
  25. claude_mpm/services/monitor/daemon.py +256 -0
  26. claude_mpm/services/monitor/event_emitter.py +279 -0
  27. claude_mpm/services/monitor/handlers/__init__.py +20 -0
  28. claude_mpm/services/monitor/handlers/code_analysis.py +334 -0
  29. claude_mpm/services/monitor/handlers/dashboard.py +298 -0
  30. claude_mpm/services/monitor/handlers/hooks.py +491 -0
  31. claude_mpm/services/monitor/management/__init__.py +18 -0
  32. claude_mpm/services/monitor/management/health.py +124 -0
  33. claude_mpm/services/monitor/management/lifecycle.py +298 -0
  34. claude_mpm/services/monitor/server.py +442 -0
  35. claude_mpm/tools/code_tree_analyzer.py +33 -17
  36. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/METADATA +1 -1
  37. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/RECORD +41 -36
  38. claude_mpm/cli/commands/socketio_monitor.py +0 -233
  39. claude_mpm/scripts/socketio_daemon.py +0 -571
  40. claude_mpm/scripts/socketio_daemon_hardened.py +0 -937
  41. claude_mpm/scripts/socketio_daemon_wrapper.py +0 -78
  42. claude_mpm/scripts/socketio_server_manager.py +0 -349
  43. claude_mpm/services/cli/dashboard_launcher.py +0 -423
  44. claude_mpm/services/cli/socketio_manager.py +0 -595
  45. claude_mpm/services/dashboard/stable_server.py +0 -1020
  46. claude_mpm/services/socketio/monitor_server.py +0 -505
  47. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/WHEEL +0 -0
  48. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/entry_points.txt +0 -0
  49. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/licenses/LICENSE +0 -0
  50. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/top_level.txt +0 -0
@@ -59,6 +59,11 @@ class CodeTree {
59
59
  this.focusedNode = null; // Track the currently focused directory
60
60
  this.horizontalNodes = new Set(); // Track nodes that should have horizontal text
61
61
  this.centralSpine = new Set(); // Track the main path through the tree
62
+
63
+ // CRITICAL FIX: Bind methods to preserve 'this' context when called by D3 event handlers
64
+ this.onNodeClick = this.onNodeClick.bind(this);
65
+ this.showTooltip = this.showTooltip.bind(this);
66
+ this.hideTooltip = this.hideTooltip.bind(this);
62
67
  }
63
68
 
64
69
  /**
@@ -292,9 +297,9 @@ class CodeTree {
292
297
 
293
298
  // Add tree controls toolbar
294
299
  this.addTreeControls();
295
-
296
- // Add breadcrumb navigation
297
- this.addBreadcrumb();
300
+
301
+ // Breadcrumb navigation removed - redundant with root node display
302
+ // this.addBreadcrumb();
298
303
 
299
304
  if (!container || !container.node()) {
300
305
  console.error('Code tree container not found');
@@ -424,16 +429,20 @@ class CodeTree {
424
429
  */
425
430
  initializeTreeData() {
426
431
  const workingDir = this.getWorkingDirectory();
427
- const dirName = workingDir ? workingDir.split('/').pop() || 'Project Root' : 'Project Root';
432
+ // Use a more descriptive root name that doesn't look like a clickable button
433
+ const dirName = 'Project Root'; // Always use generic name for root
428
434
 
429
435
  // Use absolute path for consistency with API expectations
430
436
  this.treeData = {
431
437
  name: dirName,
432
438
  path: workingDir || '.', // Use working directory or fallback to '.'
433
439
  type: 'root',
440
+ isDirectory: true, // Critical: Mark root as directory for proper handling
434
441
  children: [],
435
442
  loaded: false,
436
- expanded: true // Start expanded
443
+ expanded: true, // Start expanded
444
+ hasChildren: true, // Root always has potential children
445
+ isRoot: true // Mark as root node for special handling
437
446
  };
438
447
 
439
448
  if (typeof d3 !== 'undefined') {
@@ -515,7 +524,6 @@ class CodeTree {
515
524
  return;
516
525
  }
517
526
 
518
-
519
527
  this.autoDiscovered = true;
520
528
  this.analyzing = true;
521
529
 
@@ -530,54 +538,111 @@ class CodeTree {
530
538
  lines: 0
531
539
  };
532
540
 
533
- // Subscribe to events if not already done
541
+ // Subscribe to events if not already done (for other features)
534
542
  if (this.socket && !this.socket.hasListeners('code:node:found')) {
535
543
  this.setupEventHandlers();
536
544
  }
537
545
 
538
- // Update tree data with working directory as the root
539
- const dirName = workingDir.split('/').pop() || 'Project Root';
540
- this.treeData = {
541
- name: dirName,
542
- path: workingDir, // Use absolute path for consistency with API expectations
543
- type: 'root',
544
- children: [],
545
- loaded: false,
546
- expanded: true // Start expanded to show discovered items
547
- };
548
-
549
- if (typeof d3 !== 'undefined') {
550
- this.root = d3.hierarchy(this.treeData);
551
- this.root.x0 = this.height / 2;
552
- this.root.y0 = 0;
553
- }
554
-
555
546
  // Update UI
556
547
  this.showLoading();
548
+ const dirName = workingDir.split('/').pop() || 'Project Root';
557
549
  this.updateBreadcrumb(`Discovering structure in ${dirName}...`, 'info');
558
550
 
559
- // Get selected languages from checkboxes
560
- const selectedLanguages = this.getSelectedLanguages();
561
-
562
- // Get ignore patterns
563
- const ignorePatterns = document.getElementById('ignore-patterns')?.value || '';
551
+ // Use REST API for initial discovery (more reliable than WebSocket)
552
+ console.log(`🚀 [ROOT DISCOVERY] Using REST API for root: ${workingDir}`);
564
553
 
565
- // Enhanced debug logging
554
+ const apiUrl = `${window.location.origin}/api/directory?path=${encodeURIComponent(workingDir)}`;
566
555
 
567
- // Request top-level discovery with working directory
568
- const requestPayload = {
569
- path: workingDir, // Use working directory instead of '.'
570
- depth: 'top_level',
571
- languages: selectedLanguages,
572
- ignore_patterns: ignorePatterns,
573
- request_id: `discover_${Date.now()}` // Add request ID for tracking
574
- };
575
-
576
- // Sending top-level discovery request
577
-
578
- if (this.socket) {
579
- this.socket.emit('code:discover:top_level', requestPayload);
580
- }
556
+ fetch(apiUrl)
557
+ .then(response => {
558
+ if (!response.ok) {
559
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
560
+ }
561
+ return response.json();
562
+ })
563
+ .then(data => {
564
+ console.log('✅ [ROOT DISCOVERY] REST API response:', data);
565
+
566
+ // Process the response
567
+ if (data.contents && Array.isArray(data.contents)) {
568
+ // Update root node with children
569
+ this.treeData.children = data.contents.map(item => ({
570
+ name: item.name,
571
+ path: item.path,
572
+ type: item.type || (item.is_directory ? 'directory' : 'file'),
573
+ size: item.size,
574
+ hasChildren: item.is_directory,
575
+ children: [],
576
+ loaded: false
577
+ }));
578
+
579
+ this.treeData.loaded = true;
580
+
581
+ // Update D3 hierarchy
582
+ if (typeof d3 !== 'undefined') {
583
+ this.root = d3.hierarchy(this.treeData);
584
+ this.root.x0 = this.height / 2;
585
+ this.root.y0 = 0;
586
+
587
+ // Expand root node to show children
588
+ if (this.root.children) {
589
+ this.root.children.forEach(child => {
590
+ child._children = null;
591
+ });
592
+ }
593
+ }
594
+
595
+ // Update the visualization
596
+ if (this.svg) {
597
+ this.update(this.root);
598
+ }
599
+
600
+ // Update stats
601
+ const fileCount = data.contents.filter(item => !item.is_directory).length;
602
+ const dirCount = data.contents.filter(item => item.is_directory).length;
603
+
604
+ this.stats.files = fileCount;
605
+ this.updateStats();
606
+
607
+ this.updateBreadcrumb(
608
+ `📁 Found ${dirCount} directories and ${fileCount} files`,
609
+ 'success'
610
+ );
611
+
612
+ this.updateActivityTicker(
613
+ `📁 Found ${dirCount} directories and ${fileCount} files`,
614
+ 'success'
615
+ );
616
+
617
+ // Add to events display
618
+ this.addEventToDisplay(
619
+ `📁 Loaded ${data.contents.length} items from project root`,
620
+ 'info'
621
+ );
622
+ }
623
+
624
+ this.analyzing = false;
625
+ this.hideLoading();
626
+ })
627
+ .catch(error => {
628
+ console.error('[ROOT DISCOVERY] Error:', error);
629
+ this.analyzing = false;
630
+ this.hideLoading();
631
+ this.showNotification(`Failed to load directory: ${error.message}`, 'error');
632
+
633
+ // Try WebSocket as fallback
634
+ if (this.socket) {
635
+ console.log('[ROOT DISCOVERY] Falling back to WebSocket');
636
+ const requestPayload = {
637
+ path: workingDir,
638
+ depth: 'top_level',
639
+ languages: this.getSelectedLanguages(),
640
+ ignore_patterns: document.getElementById('ignore-patterns')?.value || '',
641
+ request_id: `discover_${Date.now()}`
642
+ };
643
+ this.socket.emit('code:discover:top_level', requestPayload);
644
+ }
645
+ });
581
646
 
582
647
  // Update stats display
583
648
  this.updateStats();
@@ -761,7 +826,7 @@ class CodeTree {
761
826
  if (!this.root) return;
762
827
 
763
828
  const expandNode = (node) => {
764
- if (node.data.type === 'directory' && node.data.loaded === true) {
829
+ if ((node.data.type === 'directory' || node.data.type === 'root' || node.data.isDirectory) && node.data.loaded === true) {
765
830
  if (node._children) {
766
831
  node.children = node._children;
767
832
  node._children = null;
@@ -785,7 +850,7 @@ class CodeTree {
785
850
  if (!this.root) return;
786
851
 
787
852
  const collapseNode = (node) => {
788
- if (node.data.type === 'directory' && node.children) {
853
+ if ((node.data.type === 'directory' || node.data.type === 'root' || node.data.isDirectory) && node.children) {
789
854
  node._children = node.children;
790
855
  node.children = null;
791
856
  node.data.expanded = false;
@@ -822,7 +887,7 @@ class CodeTree {
822
887
  navigateToPath(path) {
823
888
  // Implementation for navigating to a specific path
824
889
  // This would expand the tree to show the specified path
825
- this.updateBreadcrumbPath(path);
890
+ // this.updateBreadcrumbPath(path); // Breadcrumb removed
826
891
  this.showNotification(`Navigating to: ${path}`, 'info');
827
892
  }
828
893
 
@@ -1559,18 +1624,22 @@ class CodeTree {
1559
1624
  line: elem.line,
1560
1625
  methods: elem.methods ? elem.methods.length : 0
1561
1626
  })));
1627
+
1628
+ // Show success message with element count breakdown
1629
+ const fileName = data.path.split('/').pop();
1630
+ const elementCounts = this.getElementCounts(data.elements);
1631
+ const summary = this.formatElementSummary(elementCounts);
1632
+
1633
+ this.showNotification(`${fileName} - ${summary}`, 'success');
1634
+ this.updateBreadcrumb(`${fileName} - AST parsed: ${summary}`, 'success');
1562
1635
  } else {
1563
1636
  const fileName = data.path.split('/').pop();
1564
1637
  console.log('⚠️ [AST ELEMENTS] No elements found in analysis result');
1565
1638
 
1566
- // Show user-friendly message for files with no AST elements
1567
- if (fileName.endsWith('__init__.py')) {
1568
- this.showNotification(`${fileName} is empty or contains only imports`, 'info');
1569
- this.updateBreadcrumb(`${fileName} - no code elements to display`, 'info');
1570
- } else {
1571
- this.showNotification(`${fileName} contains no classes or functions`, 'info');
1572
- this.updateBreadcrumb(`${fileName} - no AST elements found`, 'info');
1573
- }
1639
+ // Show accurate message for files with no structural AST elements
1640
+ const fileType = this.getFileTypeDescription(fileName);
1641
+ this.showNotification(`${fileName} - No structural elements to display in tree`, 'info');
1642
+ this.updateBreadcrumb(`${fileName} - ${fileType} analyzed, content not suitable for tree view`, 'info');
1574
1643
  }
1575
1644
 
1576
1645
  // Clear analysis timeout
@@ -1624,6 +1693,9 @@ class CodeTree {
1624
1693
  childrenCount: children.length,
1625
1694
  children: children.map(c => ({ name: c.name, type: c.type }))
1626
1695
  });
1696
+
1697
+ // Auto-expand the file node to show AST tree if it was recently clicked
1698
+ this.autoExpandFileWithAST(data.path, fileNode);
1627
1699
  } else {
1628
1700
  console.log('⚠️ [FILE NODE] No elements to add as children');
1629
1701
  }
@@ -2439,7 +2511,7 @@ class CodeTree {
2439
2511
  if (children.length === 1) return children[0];
2440
2512
 
2441
2513
  // Prioritize directories over files
2442
- const directories = children.filter(child => child.data.type === 'directory');
2514
+ const directories = children.filter(child => this.isNodeDirectory(child));
2443
2515
  if (directories.length === 1) return directories[0];
2444
2516
 
2445
2517
  // If multiple directories, choose the first one (could be enhanced with better logic)
@@ -2580,7 +2652,7 @@ class CodeTree {
2580
2652
  const nodeEnter = node.enter().append('g')
2581
2653
  .attr('class', d => {
2582
2654
  let classes = ['node', 'code-node'];
2583
- if (d.data.type === 'directory') {
2655
+ if (this.isNodeDirectory(d)) {
2584
2656
  classes.push('directory');
2585
2657
  if (d.data.loaded === true && d.children) {
2586
2658
  classes.push('expanded');
@@ -2604,7 +2676,17 @@ class CodeTree {
2604
2676
  return `translate(${source.y0},${source.x0})`;
2605
2677
  }
2606
2678
  })
2607
- .on('click', (event, d) => this.onNodeClick(event, d));
2679
+ .on('click', (event, d) => {
2680
+ console.log('🔴 [G-ELEMENT] Click on node group element!', {
2681
+ nodeName: d?.data?.name,
2682
+ nodePath: d?.data?.path,
2683
+ eventTarget: event.target.tagName,
2684
+ thisContext: this,
2685
+ hasOnNodeClick: typeof this.onNodeClick === 'function'
2686
+ });
2687
+ // Use bound method
2688
+ this.onNodeClick(event, d);
2689
+ });
2608
2690
 
2609
2691
  // Add circles for nodes
2610
2692
  nodeEnter.append('circle')
@@ -2612,14 +2694,22 @@ class CodeTree {
2612
2694
  .attr('r', 1e-6)
2613
2695
  .style('fill', d => this.getNodeColor(d))
2614
2696
  .style('stroke', d => this.getNodeStrokeColor(d))
2615
- .style('stroke-width', d => d.data.type === 'directory' ? 2 : 1.5)
2616
- .style('cursor', 'pointer') // Add cursor pointer for visual feedback
2617
- .on('click', (event, d) => this.onNodeClick(event, d)) // CRITICAL FIX: Add click handler to circles
2618
- .on('mouseover', (event, d) => this.showTooltip(event, d))
2619
- .on('mouseout', () => this.hideTooltip());
2697
+ .style('stroke-width', d => this.isNodeDirectory(d) ? 2 : 1.5)
2698
+ .style('cursor', d => (d.data && (d.data.type === 'root' || d.data.isRoot || d.depth === 0)) ? 'default' : 'pointer') // No pointer for root
2699
+ .on('click', (event, d) => {
2700
+ console.log('🔵 [CIRCLE] Click on circle element!', {
2701
+ nodeName: d?.data?.name,
2702
+ nodePath: d?.data?.path,
2703
+ hasOnNodeClick: typeof this.onNodeClick === 'function'
2704
+ });
2705
+ // Use bound method
2706
+ this.onNodeClick(event, d);
2707
+ }) // CRITICAL FIX: Add click handler to circles
2708
+ .on('mouseover', this.showTooltip)
2709
+ .on('mouseout', this.hideTooltip);
2620
2710
 
2621
2711
  // Add expand/collapse icons for directories
2622
- nodeEnter.filter(d => d.data.type === 'directory')
2712
+ nodeEnter.filter(d => this.isNodeDirectory(d))
2623
2713
  .append('text')
2624
2714
  .attr('class', 'expand-icon')
2625
2715
  .attr('x', 0)
@@ -2704,11 +2794,19 @@ class CodeTree {
2704
2794
  }
2705
2795
  return null;
2706
2796
  })
2707
- .on('click', (event, d) => this.onNodeClick(event, d)) // CRITICAL FIX: Add click handler to labels
2708
- .style('cursor', 'pointer');
2797
+ .on('click', (event, d) => {
2798
+ console.log('📝 [LABEL] Click on text label!', {
2799
+ nodeName: d?.data?.name,
2800
+ nodePath: d?.data?.path,
2801
+ hasOnNodeClick: typeof this.onNodeClick === 'function'
2802
+ });
2803
+ // Use bound method
2804
+ this.onNodeClick(event, d);
2805
+ }) // CRITICAL FIX: Add click handler to labels
2806
+ .style('cursor', d => (d.data && (d.data.type === 'root' || d.data.isRoot || d.depth === 0)) ? 'default' : 'pointer');
2709
2807
 
2710
2808
  // Add icons for node types (files only, directories use expand icons)
2711
- nodeEnter.filter(d => d.data.type !== 'directory')
2809
+ nodeEnter.filter(d => !this.isNodeDirectory(d))
2712
2810
  .append('text')
2713
2811
  .attr('class', 'node-icon')
2714
2812
  .attr('dy', '.35em')
@@ -2717,11 +2815,11 @@ class CodeTree {
2717
2815
  .text(d => this.getNodeIcon(d))
2718
2816
  .style('font-size', '10px')
2719
2817
  .style('fill', 'white')
2720
- .on('click', (event, d) => this.onNodeClick(event, d)) // CRITICAL FIX: Add click handler to file icons
2818
+ .on('click', this.onNodeClick) // CRITICAL FIX: Add click handler to file icons
2721
2819
  .style('cursor', 'pointer');
2722
2820
 
2723
2821
  // Add item count badges for directories
2724
- nodeEnter.filter(d => d.data.type === 'directory' && d.data.children)
2822
+ nodeEnter.filter(d => this.isNodeDirectory(d) && d.data.children)
2725
2823
  .append('text')
2726
2824
  .attr('class', 'item-count-badge')
2727
2825
  .attr('x', 12)
@@ -2733,7 +2831,7 @@ class CodeTree {
2733
2831
  })
2734
2832
  .style('font-size', '9px')
2735
2833
  .style('opacity', 0.7)
2736
- .on('click', (event, d) => this.onNodeClick(event, d)) // CRITICAL FIX: Add click handler to count badges
2834
+ .on('click', this.onNodeClick) // CRITICAL FIX: Add click handler to count badges
2737
2835
  .style('cursor', 'pointer');
2738
2836
 
2739
2837
  // Transition to new positions
@@ -2741,11 +2839,20 @@ class CodeTree {
2741
2839
 
2742
2840
  // CRITICAL FIX: Ensure ALL nodes (new and existing) have click handlers
2743
2841
  // This fixes the issue where subdirectory clicks stop working after tree updates
2744
- nodeUpdate.on('click', (event, d) => this.onNodeClick(event, d));
2842
+ nodeUpdate.on('click', (event, d) => {
2843
+ console.log('🟡 [NODE-UPDATE] Click on updated node!', {
2844
+ nodeName: d?.data?.name,
2845
+ nodePath: d?.data?.path,
2846
+ hasOnNodeClick: typeof this.onNodeClick === 'function',
2847
+ thisContext: this
2848
+ });
2849
+ // Use bound method
2850
+ this.onNodeClick(event, d);
2851
+ });
2745
2852
 
2746
- // ADDITIONAL FIX: Also ensure click handlers on all child elements
2747
- nodeUpdate.selectAll('circle').on('click', (event, d) => this.onNodeClick(event, d));
2748
- nodeUpdate.selectAll('text').on('click', (event, d) => this.onNodeClick(event, d));
2853
+ // ADDITIONAL FIX: Also ensure click handlers on all child elements using bound methods
2854
+ nodeUpdate.selectAll('circle').on('click', this.onNodeClick);
2855
+ nodeUpdate.selectAll('text').on('click', this.onNodeClick);
2749
2856
 
2750
2857
  nodeUpdate.transition()
2751
2858
  .duration(this.duration)
@@ -2761,7 +2868,7 @@ class CodeTree {
2761
2868
  // Update node classes based on current state
2762
2869
  nodeUpdate.attr('class', d => {
2763
2870
  let classes = ['node', 'code-node'];
2764
- if (d.data.type === 'directory') {
2871
+ if (this.isNodeDirectory(d)) {
2765
2872
  classes.push('directory');
2766
2873
  if (d.data.loaded === true && d.children) {
2767
2874
  classes.push('expanded');
@@ -2779,7 +2886,7 @@ class CodeTree {
2779
2886
  });
2780
2887
 
2781
2888
  nodeUpdate.select('circle.node-circle')
2782
- .attr('r', d => d.data.type === 'directory' ? 10 : 8)
2889
+ .attr('r', d => this.isNodeDirectory(d) ? 10 : 8)
2783
2890
  .style('fill', d => this.getNodeColor(d))
2784
2891
 
2785
2892
  // Update expand/collapse icons
@@ -2793,7 +2900,7 @@ class CodeTree {
2793
2900
  // Update item count badges
2794
2901
  nodeUpdate.select('.item-count-badge')
2795
2902
  .text(d => {
2796
- if (d.data.type !== 'directory') return '';
2903
+ if (!this.isNodeDirectory(d)) return '';
2797
2904
  const count = d.data.children ? d.data.children.length : 0;
2798
2905
  return count > 0 ? count : '';
2799
2906
  })
@@ -3104,7 +3211,9 @@ class CodeTree {
3104
3211
  onNodeClick(event, d) {
3105
3212
  const clickId = Date.now() + Math.random();
3106
3213
  // DEBUG: Log all clicks to verify handler is working
3107
- console.log(`🖱️ [NODE CLICK] Clicked on node (ID: ${clickId}):`, {
3214
+ console.log(`🖱️🖱️🖱️ [NODE CLICK] onNodeClick method called! (ID: ${clickId}):`, {
3215
+ thisContext: this,
3216
+ isBound: this.constructor.name === 'CodeTree',
3108
3217
  name: d?.data?.name,
3109
3218
  path: d?.data?.path,
3110
3219
  type: d?.data?.type,
@@ -3137,7 +3246,13 @@ class CodeTree {
3137
3246
  console.error('[CodeTree] ERROR: d is null/undefined, cannot continue');
3138
3247
  return;
3139
3248
  }
3140
-
3249
+
3250
+ // Ignore clicks on root node - it should not be interactive
3251
+ if (d.data && (d.data.type === 'root' || d.data.isRoot || d.depth === 0)) {
3252
+ console.log('🚫 [ROOT CLICK] Ignoring click on root node:', d.data.name);
3253
+ return;
3254
+ }
3255
+
3141
3256
  if (!d.data) {
3142
3257
  console.error('[CodeTree] ERROR: d.data is null/undefined, cannot continue');
3143
3258
  return;
@@ -3197,7 +3312,7 @@ class CodeTree {
3197
3312
 
3198
3313
  // Add pulsing animation immediately for directories
3199
3314
 
3200
- if (d.data.type === 'directory' && !d.data.loaded) {
3315
+ if (this.isNodeDirectory(d) && !d.data.loaded) {
3201
3316
  try {
3202
3317
  if (typeof this.addLoadingPulse === 'function') {
3203
3318
  this.addLoadingPulse(d);
@@ -3231,11 +3346,12 @@ class CodeTree {
3231
3346
  type: d.data.type,
3232
3347
  loaded: d.data.loaded,
3233
3348
  loadedType: typeof d.data.loaded,
3234
- isDirectory: d.data.type === 'directory',
3349
+ isDirectory: d.data.type === 'directory' || d.data.type === 'root',
3235
3350
  notLoaded: !d.data.loaded,
3236
- shouldLoad: d.data.type === 'directory' && !d.data.loaded
3351
+ shouldLoad: (d.data.type === 'directory' || d.data.type === 'root') && !d.data.loaded
3237
3352
  });
3238
- if (d.data.type === 'directory' && !d.data.loaded) {
3353
+ // Check for both 'directory' type and 'root' type (or use isDirectory flag)
3354
+ if ((d.data.type === 'directory' || d.data.type === 'root' || d.data.isDirectory) && !d.data.loaded) {
3239
3355
  console.log('✅ [SUBDIRECTORY LOADING] Load check passed, proceeding with loading logic');
3240
3356
  console.log('🔍 [SUBDIRECTORY LOADING] Initial loading state:', {
3241
3357
  loadingNodesSize: this.loadingNodes ? this.loadingNodes.size : 'undefined',
@@ -3306,12 +3422,12 @@ class CodeTree {
3306
3422
  console.log('📡 [SUBDIRECTORY LOADING] Using REST API for directory:', {
3307
3423
  originalPath: d.data.path,
3308
3424
  fullPath: fullPath,
3309
- apiUrl: `${window.location.origin}/api/directory/list?path=${encodeURIComponent(fullPath)}`,
3425
+ apiUrl: `${window.location.origin}/api/directory?path=${encodeURIComponent(fullPath)}`,
3310
3426
  loadingNodesSize: this.loadingNodes.size,
3311
3427
  loadingNodesContent: Array.from(this.loadingNodes)
3312
3428
  });
3313
3429
 
3314
- const apiUrl = `${window.location.origin}/api/directory/list?path=${encodeURIComponent(fullPath)}`;
3430
+ const apiUrl = `${window.location.origin}/api/directory?path=${encodeURIComponent(fullPath)}`;
3315
3431
 
3316
3432
  fetch(apiUrl)
3317
3433
  .then(response => {
@@ -3450,28 +3566,40 @@ class CodeTree {
3450
3566
  this.showNotification(`Error loading directory: ${error.message}`, 'error');
3451
3567
  }
3452
3568
  }
3453
- // For files that haven't been analyzed, request analysis
3454
- else if (d.data.type === 'file' && !d.data.analyzed) {
3455
- // Only analyze files of selected languages
3456
- const fileLanguage = this.detectLanguage(d.data.path);
3457
- console.log('🔍 [FILE ANALYSIS] Language check:', {
3569
+ // For files, handle both content display and analysis
3570
+ else if (d.data.type === 'file') {
3571
+ console.log('📄 [FILE CLICK] File clicked:', {
3458
3572
  fileName: d.data.name,
3459
3573
  filePath: d.data.path,
3460
- detectedLanguage: fileLanguage,
3461
- selectedLanguages: selectedLanguages,
3462
- isLanguageSelected: selectedLanguages.includes(fileLanguage),
3463
- shouldAnalyze: selectedLanguages.includes(fileLanguage) || fileLanguage === 'unknown'
3574
+ analyzed: d.data.analyzed
3464
3575
  });
3465
3576
 
3466
- if (!selectedLanguages.includes(fileLanguage) && fileLanguage !== 'unknown') {
3467
- console.warn('⚠️ [FILE ANALYSIS] Skipping file:', {
3577
+ // PHASE 1: Display file content in unified data viewer
3578
+ this.displayFileInDataViewer(d);
3579
+
3580
+ // PHASE 2: Handle AST analysis for code files (if not already analyzed)
3581
+ if (!d.data.analyzed) {
3582
+ // Only analyze files of selected languages
3583
+ const fileLanguage = this.detectLanguage(d.data.path);
3584
+ console.log('🔍 [FILE ANALYSIS] Language check:', {
3468
3585
  fileName: d.data.name,
3586
+ filePath: d.data.path,
3469
3587
  detectedLanguage: fileLanguage,
3470
3588
  selectedLanguages: selectedLanguages,
3471
- reason: `${fileLanguage} not in selected languages`
3589
+ isLanguageSelected: selectedLanguages.includes(fileLanguage),
3590
+ shouldAnalyze: selectedLanguages.includes(fileLanguage) || fileLanguage === 'unknown'
3472
3591
  });
3473
- this.showNotification(`Skipping ${d.data.name} - ${fileLanguage} not selected`, 'warning');
3474
- return;
3592
+
3593
+ if (!selectedLanguages.includes(fileLanguage) && fileLanguage !== 'unknown') {
3594
+ console.warn('⚠️ [FILE ANALYSIS] Skipping AST analysis for file:', {
3595
+ fileName: d.data.name,
3596
+ detectedLanguage: fileLanguage,
3597
+ selectedLanguages: selectedLanguages,
3598
+ reason: `${fileLanguage} not in selected languages`
3599
+ });
3600
+ // Still show file content, just skip AST analysis
3601
+ return;
3602
+ }
3475
3603
  }
3476
3604
 
3477
3605
  // Add pulsing animation immediately
@@ -3526,7 +3654,7 @@ class CodeTree {
3526
3654
  }, 100); // 100ms delay to ensure visual effects render first
3527
3655
  }
3528
3656
  // Toggle children visibility for already loaded nodes
3529
- else if (d.data.type === 'directory' && d.data.loaded === true) {
3657
+ else if (this.isNodeDirectory(d) && d.data.loaded === true) {
3530
3658
  // Directory is loaded, toggle expansion
3531
3659
  if (d.children) {
3532
3660
  // Collapse - hide children
@@ -3652,6 +3780,14 @@ class CodeTree {
3652
3780
  return path({source: s, target: d});
3653
3781
  }
3654
3782
 
3783
+ /**
3784
+ * Helper function to check if a node is a directory or root
3785
+ */
3786
+ isNodeDirectory(node) {
3787
+ const data = node.data || node;
3788
+ return data.type === 'directory' || data.type === 'root' || data.isDirectory === true;
3789
+ }
3790
+
3655
3791
  /**
3656
3792
  * Get node color based on type and complexity
3657
3793
  */
@@ -3689,7 +3825,7 @@ class CodeTree {
3689
3825
  if (d.data.loaded === 'loading' || d.data.analyzed === 'loading') {
3690
3826
  return '#FCD34D'; // Yellow for loading
3691
3827
  }
3692
- if (d.data.type === 'directory' && !d.data.loaded) {
3828
+ if (this.isNodeDirectory(d) && !d.data.loaded) {
3693
3829
  return '#94A3B8'; // Gray for unloaded
3694
3830
  }
3695
3831
  if (d.data.type === 'file' && !d.data.analyzed) {
@@ -3738,7 +3874,7 @@ class CodeTree {
3738
3874
  }
3739
3875
 
3740
3876
  // Special messages for lazy-loaded nodes
3741
- if (d.data.type === 'directory' && !d.data.loaded) {
3877
+ if (this.isNodeDirectory(d) && !d.data.loaded) {
3742
3878
  info.push('<em>Click to explore contents</em>');
3743
3879
  } else if (d.data.type === 'file' && !d.data.analyzed) {
3744
3880
  info.push('<em>Click to analyze file</em>');
@@ -4084,11 +4220,19 @@ class CodeTree {
4084
4220
  * Show hierarchical source viewer for a source file
4085
4221
  */
4086
4222
  async showSourceViewer(node) {
4087
- console.log('📄 [SOURCE VIEWER] Showing source for:', node.data.path);
4223
+ console.log('📄 [SOURCE VIEWER] Starting showSourceViewer for:', node.data.path);
4224
+ console.log(' Node type:', node.data.type);
4225
+ console.log(' Content element available:', !!this.structuredDataContent);
4226
+
4227
+ if (!this.structuredDataContent) {
4228
+ console.error('❌ [SOURCE VIEWER] No content element to display source in!');
4229
+ return;
4230
+ }
4088
4231
 
4089
4232
  // Create source viewer container
4090
4233
  const sourceViewer = document.createElement('div');
4091
4234
  sourceViewer.className = 'source-viewer';
4235
+ console.log('📦 [SOURCE VIEWER] Created source viewer container');
4092
4236
 
4093
4237
  // Create header
4094
4238
  const header = document.createElement('div');
@@ -4108,7 +4252,11 @@ class CodeTree {
4108
4252
 
4109
4253
  sourceViewer.appendChild(header);
4110
4254
  sourceViewer.appendChild(content);
4255
+ console.log('🔨 [SOURCE VIEWER] Appending source viewer to content element...');
4111
4256
  this.structuredDataContent.appendChild(sourceViewer);
4257
+ console.log('✅ [SOURCE VIEWER] Source viewer added to DOM');
4258
+ console.log(' Content element children count:', this.structuredDataContent.children.length);
4259
+ console.log(' Content element HTML preview:', this.structuredDataContent.innerHTML.substring(0, 200) + '...');
4112
4260
 
4113
4261
  // Add control event listeners
4114
4262
  document.getElementById('expand-all-source')?.addEventListener('click', () => this.expandAllSource());
@@ -4378,39 +4526,98 @@ class CodeTree {
4378
4526
  * Initialize the structured data integration
4379
4527
  */
4380
4528
  initializeStructuredData() {
4381
- // Try to find the Code tab content area first
4382
- this.structuredDataContent = document.getElementById('code-module-data-content');
4529
+ console.log('🔄 [CODE TREE] Initializing structured data integration...');
4383
4530
 
4384
- // Fall back to Events tab content area for backward compatibility
4385
- if (!this.structuredDataContent) {
4386
- this.structuredDataContent = document.getElementById('module-data-content');
4387
- }
4531
+ // Use the existing "📊 Structured Data" section in the left panel
4532
+ this.structuredDataContent = document.getElementById('module-data-content');
4388
4533
 
4389
4534
  if (!this.structuredDataContent) {
4390
- console.warn('Structured data content element not found');
4535
+ console.warn('⏳ [CODE TREE] Structured data element not found yet, retrying in 500ms...');
4536
+
4537
+ // Retry after a short delay in case DOM is still loading
4538
+ setTimeout(() => {
4539
+ this.structuredDataContent = document.getElementById('module-data-content');
4540
+
4541
+ if (!this.structuredDataContent) {
4542
+ console.error('❌ [CODE TREE] Structured data content element (#module-data-content) not found after retry!');
4543
+ console.log('[CODE TREE] Checking DOM for available elements...');
4544
+ // Debug: List all elements with module or data in their ID
4545
+ const allElements = document.querySelectorAll('[id*="module"], [id*="data"]');
4546
+ console.log(`[CODE TREE] Found ${allElements.length} elements with "module" or "data" in ID:`);
4547
+ allElements.forEach(el => {
4548
+ console.log(` - #${el.id} (class: ${el.className}, parent: ${el.parentElement?.id || 'no-parent'})`);
4549
+ });
4550
+
4551
+ // Also check for the module viewer container
4552
+ const moduleViewer = document.querySelector('.module-viewer');
4553
+ if (moduleViewer) {
4554
+ console.log('[CODE TREE] Module viewer found, checking children...');
4555
+ const moduleDataContent = moduleViewer.querySelector('#module-data-content');
4556
+ if (moduleDataContent) {
4557
+ console.log('[CODE TREE] Found module-data-content via query selector!');
4558
+ this.structuredDataContent = moduleDataContent;
4559
+ } else {
4560
+ console.log('[CODE TREE] Module data content not found in module viewer');
4561
+ }
4562
+ }
4563
+ } else {
4564
+ console.log('✅ [CODE TREE] Structured data integration initialized on retry');
4565
+ console.log(' Target element:', this.structuredDataContent);
4566
+ console.log(' Parent element:', this.structuredDataContent.parentElement);
4567
+ }
4568
+ }, 500);
4391
4569
  return;
4392
4570
  }
4393
4571
 
4394
- console.log('✅ Structured data integration initialized');
4572
+ console.log('✅ [CODE TREE] Structured data integration initialized immediately');
4573
+ console.log(' Target element:', this.structuredDataContent);
4574
+ console.log(' Parent element:', this.structuredDataContent.parentElement);
4395
4575
  }
4396
4576
 
4397
4577
  /**
4398
4578
  * Update structured data with node information
4399
4579
  */
4400
4580
  updateStructuredData(node) {
4581
+ console.log('📝 [STRUCTURED DATA] updateStructuredData called');
4582
+
4401
4583
  if (!this.structuredDataContent) {
4402
- return;
4584
+ console.warn('⚠️ [STRUCTURED DATA] Content element not available, trying to find it...');
4585
+ // Try to find it again in case it wasn't initialized properly
4586
+ this.structuredDataContent = document.getElementById('module-data-content');
4587
+
4588
+ if (!this.structuredDataContent) {
4589
+ console.error('❌ [STRUCTURED DATA] Cannot find module-data-content element!');
4590
+ // Last resort - try to find it in module viewer
4591
+ const moduleViewer = document.querySelector('.module-viewer');
4592
+ if (moduleViewer) {
4593
+ this.structuredDataContent = moduleViewer.querySelector('#module-data-content');
4594
+ if (this.structuredDataContent) {
4595
+ console.log('✅ [STRUCTURED DATA] Found element via module-viewer query');
4596
+ } else {
4597
+ console.error('❌ [STRUCTURED DATA] Still cannot find element, aborting update');
4598
+ return;
4599
+ }
4600
+ } else {
4601
+ console.error('❌ [STRUCTURED DATA] Module viewer not found either, aborting update');
4602
+ return;
4603
+ }
4604
+ } else {
4605
+ console.log('✅ [STRUCTURED DATA] Found element on retry');
4606
+ }
4403
4607
  }
4404
4608
 
4405
4609
  console.log('🔍 [STRUCTURED DATA] Updating with node:', {
4406
4610
  name: node?.data?.name,
4407
4611
  type: node?.data?.type,
4612
+ path: node?.data?.path,
4408
4613
  hasChildren: !!(node?.children || node?._children),
4409
- dataChildren: node?.data?.children?.length || 0
4614
+ dataChildren: node?.data?.children?.length || 0,
4615
+ contentElement: this.structuredDataContent
4410
4616
  });
4411
4617
 
4412
4618
  // Clear previous content
4413
4619
  this.structuredDataContent.innerHTML = '';
4620
+ console.log('🧹 [STRUCTURED DATA] Cleared previous content');
4414
4621
 
4415
4622
  // Check if this is a source file that should show source viewer
4416
4623
  if (node.data.type === 'file' && this.isSourceFile(node.data.path)) {
@@ -5135,6 +5342,377 @@ main();
5135
5342
  return match ? match[1].length : 0;
5136
5343
  }
5137
5344
 
5345
+ /**
5346
+ * Auto-expand file node to show AST tree after analysis
5347
+ */
5348
+ autoExpandFileWithAST(filePath, fileNode) {
5349
+ console.log('🌳 [AST EXPANSION] Auto-expanding file with AST:', {
5350
+ filePath: filePath,
5351
+ hasChildren: !!(fileNode && fileNode.children && fileNode.children.length > 0)
5352
+ });
5353
+
5354
+ if (!fileNode || !fileNode.children || fileNode.children.length === 0) {
5355
+ console.log('⚠️ [AST EXPANSION] No children to expand');
5356
+ return;
5357
+ }
5358
+
5359
+ // Find the D3 node for this file
5360
+ const d3Node = this.findD3NodeByPath(filePath);
5361
+ if (!d3Node) {
5362
+ console.log('⚠️ [AST EXPANSION] D3 node not found for path:', filePath);
5363
+ return;
5364
+ }
5365
+
5366
+ // Rebuild the D3 hierarchy to include new children
5367
+ this.root = d3.hierarchy(this.treeData);
5368
+
5369
+ // Find the updated D3 node
5370
+ const updatedD3Node = this.findD3NodeByPath(filePath);
5371
+ if (updatedD3Node) {
5372
+ // Expand the file node to show AST elements
5373
+ if (updatedD3Node._children || (updatedD3Node.children && updatedD3Node.children.length === 0)) {
5374
+ updatedD3Node.children = updatedD3Node._children || updatedD3Node.children;
5375
+ updatedD3Node._children = null;
5376
+ updatedD3Node.data.expanded = true;
5377
+
5378
+ console.log('✅ [AST EXPANSION] Expanded file node to show AST elements:', {
5379
+ filePath: filePath,
5380
+ childrenCount: updatedD3Node.children ? updatedD3Node.children.length : 0
5381
+ });
5382
+
5383
+ // Update the visualization
5384
+ this.update(this.root);
5385
+
5386
+ // Show notification about AST expansion
5387
+ const fileName = filePath.split('/').pop();
5388
+ const astCount = updatedD3Node.children ? updatedD3Node.children.length : 0;
5389
+ this.showNotification(`📊 ${fileName} - AST tree expanded with ${astCount} elements`, 'success');
5390
+ this.updateBreadcrumb(`${fileName} - Code structure visible in tree`, 'success');
5391
+ }
5392
+ }
5393
+ }
5394
+
5395
+ /**
5396
+ * Display file content in the unified data viewer
5397
+ */
5398
+ displayFileInDataViewer(d) {
5399
+ console.log('📊 [DATA VIEWER] Displaying file in data viewer:', {
5400
+ fileName: d.data.name,
5401
+ filePath: d.data.path,
5402
+ fileType: d.data.type
5403
+ });
5404
+
5405
+ // Create file data object for unified data viewer
5406
+ const fileData = {
5407
+ file_path: d.data.path,
5408
+ name: d.data.name,
5409
+ type: 'file',
5410
+ size: d.data.size || 0,
5411
+ extension: this.getFileExtension(d.data.path),
5412
+ language: this.detectLanguage(d.data.path),
5413
+ operations: [{
5414
+ operation: 'view',
5415
+ timestamp: new Date().toISOString(),
5416
+ source: 'code_tree_click'
5417
+ }],
5418
+ // Add metadata for better display
5419
+ metadata: {
5420
+ clicked_from: 'code_tree',
5421
+ node_type: d.data.type,
5422
+ has_ast: d.data.analyzed || false,
5423
+ tree_path: this.getNodePath(d)
5424
+ }
5425
+ };
5426
+
5427
+ // Use the unified data viewer to display file information
5428
+ if (window.unifiedDataViewer) {
5429
+ window.unifiedDataViewer.display(fileData, 'file_operation');
5430
+ console.log('✅ [DATA VIEWER] File data displayed in unified viewer');
5431
+ } else {
5432
+ console.warn('⚠️ [DATA VIEWER] UnifiedDataViewer not available');
5433
+ }
5434
+
5435
+ // Update module header to show current file
5436
+ const moduleHeader = document.querySelector('.module-data-header h5');
5437
+ if (moduleHeader) {
5438
+ const fileName = d.data.name;
5439
+ const fileIcon = this.getFileIcon(d.data.path);
5440
+ moduleHeader.innerHTML = `${fileIcon} File: ${fileName}`;
5441
+ }
5442
+
5443
+ // Add a small delay, then check if user wants to open full file viewer
5444
+ // This gives them time to see the data viewer content first
5445
+ setTimeout(() => {
5446
+ this.offerFileViewerOption(d);
5447
+ }, 1000);
5448
+ }
5449
+
5450
+ /**
5451
+ * Offer option to open file in full viewer modal
5452
+ */
5453
+ offerFileViewerOption(d) {
5454
+ // Only offer for text files that can be displayed
5455
+ if (!this.isTextFile(d.data.path)) {
5456
+ return;
5457
+ }
5458
+
5459
+ // Create a subtle notification with option to open full viewer
5460
+ const fileName = d.data.name;
5461
+ const notification = document.createElement('div');
5462
+ notification.className = 'file-viewer-offer';
5463
+ notification.innerHTML = `
5464
+ <div class="offer-content">
5465
+ <span class="offer-text">📄 ${fileName} loaded in data viewer</span>
5466
+ <button class="offer-button" onclick="this.parentElement.parentElement.remove(); window.showFileViewerModal && window.showFileViewerModal('${d.data.path}')">
5467
+ 🔍 Open Full Viewer
5468
+ </button>
5469
+ <button class="offer-close" onclick="this.parentElement.parentElement.remove()">×</button>
5470
+ </div>
5471
+ `;
5472
+
5473
+ // Style the notification
5474
+ notification.style.cssText = `
5475
+ position: fixed;
5476
+ top: 20px;
5477
+ right: 20px;
5478
+ background: #f8fafc;
5479
+ border: 1px solid #e2e8f0;
5480
+ border-radius: 8px;
5481
+ padding: 12px;
5482
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
5483
+ z-index: 1000;
5484
+ max-width: 300px;
5485
+ font-size: 14px;
5486
+ `;
5487
+
5488
+ // Style the button
5489
+ const style = document.createElement('style');
5490
+ style.textContent = `
5491
+ .offer-content {
5492
+ display: flex;
5493
+ align-items: center;
5494
+ gap: 8px;
5495
+ }
5496
+ .offer-text {
5497
+ flex: 1;
5498
+ color: #4a5568;
5499
+ }
5500
+ .offer-button {
5501
+ background: #4299e1;
5502
+ color: white;
5503
+ border: none;
5504
+ border-radius: 4px;
5505
+ padding: 4px 8px;
5506
+ font-size: 12px;
5507
+ cursor: pointer;
5508
+ transition: background 0.2s;
5509
+ }
5510
+ .offer-button:hover {
5511
+ background: #3182ce;
5512
+ }
5513
+ .offer-close {
5514
+ background: none;
5515
+ border: none;
5516
+ color: #a0aec0;
5517
+ cursor: pointer;
5518
+ font-size: 16px;
5519
+ padding: 0;
5520
+ width: 20px;
5521
+ height: 20px;
5522
+ display: flex;
5523
+ align-items: center;
5524
+ justify-content: center;
5525
+ }
5526
+ .offer-close:hover {
5527
+ color: #718096;
5528
+ }
5529
+ `;
5530
+
5531
+ document.head.appendChild(style);
5532
+ document.body.appendChild(notification);
5533
+
5534
+ // Auto-remove after 5 seconds
5535
+ setTimeout(() => {
5536
+ if (notification.parentElement) {
5537
+ notification.remove();
5538
+ }
5539
+ }, 5000);
5540
+ }
5541
+
5542
+ /**
5543
+ * Check if file is a text file that can be displayed
5544
+ */
5545
+ isTextFile(filePath) {
5546
+ if (!filePath) return false;
5547
+
5548
+ const ext = this.getFileExtension(filePath);
5549
+ const textExtensions = [
5550
+ 'py', 'js', 'ts', 'jsx', 'tsx', 'html', 'css', 'json', 'md', 'txt',
5551
+ 'yml', 'yaml', 'xml', 'sql', 'sh', 'bash', 'dockerfile', 'makefile',
5552
+ 'gitignore', 'readme', 'cfg', 'conf', 'ini', 'toml', 'lock'
5553
+ ];
5554
+
5555
+ return textExtensions.includes(ext) ||
5556
+ textExtensions.includes(filePath.toLowerCase().split('/').pop());
5557
+ }
5558
+
5559
+ /**
5560
+ * Get the full path from root to the given node
5561
+ */
5562
+ getNodePath(d) {
5563
+ const path = [];
5564
+ let current = d;
5565
+ while (current) {
5566
+ if (current.data && current.data.name) {
5567
+ path.unshift(current.data.name);
5568
+ }
5569
+ current = current.parent;
5570
+ }
5571
+ return path.join(' > ');
5572
+ }
5573
+
5574
+ /**
5575
+ * Get file extension from path
5576
+ */
5577
+ getFileExtension(filePath) {
5578
+ if (!filePath) return '';
5579
+ const parts = filePath.split('.');
5580
+ return parts.length > 1 ? parts.pop().toLowerCase() : '';
5581
+ }
5582
+
5583
+ /**
5584
+ * Get descriptive file type for user messages
5585
+ */
5586
+ getFileTypeDescription(fileName) {
5587
+ if (!fileName) return 'File';
5588
+
5589
+ const ext = this.getFileExtension(fileName);
5590
+ const baseName = fileName.toLowerCase();
5591
+
5592
+ // Special cases
5593
+ if (baseName.endsWith('__init__.py')) {
5594
+ return 'Python package initialization';
5595
+ }
5596
+ if (baseName === 'makefile') {
5597
+ return 'Build configuration';
5598
+ }
5599
+ if (baseName.includes('config') || baseName.includes('settings')) {
5600
+ return 'Configuration file';
5601
+ }
5602
+ if (baseName.includes('test') || baseName.includes('spec')) {
5603
+ return 'Test file';
5604
+ }
5605
+
5606
+ // By extension
5607
+ const typeMap = {
5608
+ 'py': 'Python file',
5609
+ 'js': 'JavaScript file',
5610
+ 'ts': 'TypeScript file',
5611
+ 'jsx': 'React component',
5612
+ 'tsx': 'React TypeScript component',
5613
+ 'html': 'HTML document',
5614
+ 'css': 'Stylesheet',
5615
+ 'json': 'JSON data',
5616
+ 'md': 'Markdown document',
5617
+ 'txt': 'Text file',
5618
+ 'yml': 'YAML configuration',
5619
+ 'yaml': 'YAML configuration',
5620
+ 'xml': 'XML document',
5621
+ 'sql': 'SQL script',
5622
+ 'sh': 'Shell script',
5623
+ 'bash': 'Bash script',
5624
+ 'toml': 'TOML configuration',
5625
+ 'ini': 'INI configuration'
5626
+ };
5627
+
5628
+ return typeMap[ext] || 'File';
5629
+ }
5630
+
5631
+ /**
5632
+ * Count different types of AST elements
5633
+ */
5634
+ getElementCounts(elements) {
5635
+ const counts = {
5636
+ classes: 0,
5637
+ functions: 0,
5638
+ methods: 0,
5639
+ total: elements.length
5640
+ };
5641
+
5642
+ elements.forEach(elem => {
5643
+ if (elem.type === 'class') {
5644
+ counts.classes++;
5645
+ if (elem.methods) {
5646
+ counts.methods += elem.methods.length;
5647
+ }
5648
+ } else if (elem.type === 'function') {
5649
+ counts.functions++;
5650
+ }
5651
+ });
5652
+
5653
+ return counts;
5654
+ }
5655
+
5656
+ /**
5657
+ * Format element counts into a readable summary
5658
+ */
5659
+ formatElementSummary(counts) {
5660
+ const parts = [];
5661
+
5662
+ if (counts.classes > 0) {
5663
+ parts.push(`${counts.classes} class${counts.classes !== 1 ? 'es' : ''}`);
5664
+ }
5665
+ if (counts.functions > 0) {
5666
+ parts.push(`${counts.functions} function${counts.functions !== 1 ? 's' : ''}`);
5667
+ }
5668
+ if (counts.methods > 0) {
5669
+ parts.push(`${counts.methods} method${counts.methods !== 1 ? 's' : ''}`);
5670
+ }
5671
+
5672
+ if (parts.length === 0) {
5673
+ return 'Structural elements for tree view';
5674
+ } else if (parts.length === 1) {
5675
+ return parts[0] + ' found';
5676
+ } else if (parts.length === 2) {
5677
+ return parts.join(' and ') + ' found';
5678
+ } else {
5679
+ return parts.slice(0, -1).join(', ') + ', and ' + parts[parts.length - 1] + ' found';
5680
+ }
5681
+ }
5682
+
5683
+ /**
5684
+ * Get file icon based on file type
5685
+ */
5686
+ getFileIcon(filePath) {
5687
+ if (!filePath) return '📄';
5688
+
5689
+ const ext = this.getFileExtension(filePath);
5690
+ const iconMap = {
5691
+ 'py': '🐍',
5692
+ 'js': '📜',
5693
+ 'ts': '📘',
5694
+ 'jsx': '⚛️',
5695
+ 'tsx': '⚛️',
5696
+ 'html': '🌐',
5697
+ 'css': '🎨',
5698
+ 'json': '📋',
5699
+ 'md': '📝',
5700
+ 'txt': '📄',
5701
+ 'yml': '⚙️',
5702
+ 'yaml': '⚙️',
5703
+ 'xml': '📰',
5704
+ 'sql': '🗃️',
5705
+ 'sh': '🐚',
5706
+ 'bash': '🐚',
5707
+ 'dockerfile': '🐳',
5708
+ 'makefile': '🔨',
5709
+ 'gitignore': '🚫',
5710
+ 'readme': '📖'
5711
+ };
5712
+
5713
+ return iconMap[ext] || iconMap[filePath.toLowerCase().split('/').pop()] || '📄';
5714
+ }
5715
+
5138
5716
  /**
5139
5717
  * Handle click on source line with AST elements
5140
5718
  */
@@ -5264,4 +5842,4 @@ document.addEventListener('DOMContentLoaded', () => {
5264
5842
  }
5265
5843
  });
5266
5844
  }
5267
- });/* Cache buster: 1756393851 */
5845
+ });