claude-mpm 4.1.11__py3-none-any.whl → 4.1.13__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 (41) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +8 -0
  3. claude_mpm/cli/__init__.py +1 -1
  4. claude_mpm/cli/commands/monitor.py +88 -627
  5. claude_mpm/cli/commands/mpm_init.py +127 -107
  6. claude_mpm/cli/commands/mpm_init_handler.py +24 -23
  7. claude_mpm/cli/parsers/mpm_init_parser.py +34 -28
  8. claude_mpm/core/config.py +18 -0
  9. claude_mpm/core/instruction_reinforcement_hook.py +266 -0
  10. claude_mpm/core/pm_hook_interceptor.py +105 -8
  11. claude_mpm/dashboard/static/built/components/activity-tree.js +1 -1
  12. claude_mpm/dashboard/static/built/components/code-tree.js +1 -1
  13. claude_mpm/dashboard/static/built/dashboard.js +1 -1
  14. claude_mpm/dashboard/static/built/socket-client.js +1 -1
  15. claude_mpm/dashboard/static/css/activity.css +1239 -267
  16. claude_mpm/dashboard/static/css/dashboard.css +511 -0
  17. claude_mpm/dashboard/static/dist/components/activity-tree.js +1 -1
  18. claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
  19. claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
  20. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  21. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  22. claude_mpm/dashboard/static/js/components/activity-tree.js +1193 -892
  23. claude_mpm/dashboard/static/js/components/build-tracker.js +15 -13
  24. claude_mpm/dashboard/static/js/components/code-tree.js +534 -143
  25. claude_mpm/dashboard/static/js/components/module-viewer.js +21 -7
  26. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +1066 -0
  27. claude_mpm/dashboard/static/js/connection-manager.js +1 -1
  28. claude_mpm/dashboard/static/js/dashboard.js +227 -84
  29. claude_mpm/dashboard/static/js/socket-client.js +2 -2
  30. claude_mpm/dashboard/templates/index.html +100 -23
  31. claude_mpm/services/agents/deployment/agent_template_builder.py +11 -7
  32. claude_mpm/services/cli/socketio_manager.py +39 -8
  33. claude_mpm/services/infrastructure/monitoring.py +1 -1
  34. claude_mpm/services/socketio/handlers/code_analysis.py +83 -136
  35. claude_mpm/tools/code_tree_analyzer.py +290 -202
  36. {claude_mpm-4.1.11.dist-info → claude_mpm-4.1.13.dist-info}/METADATA +1 -1
  37. {claude_mpm-4.1.11.dist-info → claude_mpm-4.1.13.dist-info}/RECORD +41 -39
  38. {claude_mpm-4.1.11.dist-info → claude_mpm-4.1.13.dist-info}/WHEEL +0 -0
  39. {claude_mpm-4.1.11.dist-info → claude_mpm-4.1.13.dist-info}/entry_points.txt +0 -0
  40. {claude_mpm-4.1.11.dist-info → claude_mpm-4.1.13.dist-info}/licenses/LICENSE +0 -0
  41. {claude_mpm-4.1.11.dist-info → claude_mpm-4.1.13.dist-info}/top_level.txt +0 -0
@@ -175,24 +175,8 @@ class CodeTree {
175
175
  toggleLegendBtn.addEventListener('click', () => this.toggleLegend());
176
176
  }
177
177
 
178
- // Listen for show hidden files toggle
179
- const showHiddenFilesCheckbox = document.getElementById('show-hidden-files');
180
- if (showHiddenFilesCheckbox) {
181
- showHiddenFilesCheckbox.addEventListener('change', () => {
182
- // Clear tree and re-discover with new settings
183
- this.autoDiscovered = false;
184
- this.initializeTreeData();
185
- this.autoDiscoverRootLevel();
186
- this.showNotification(
187
- showHiddenFilesCheckbox.checked ? 'Showing hidden files' : 'Hiding hidden files',
188
- 'info'
189
- );
190
- });
191
- }
192
-
193
178
  // Listen for working directory changes
194
179
  document.addEventListener('workingDirectoryChanged', (e) => {
195
- console.log('Working directory changed to:', e.detail.directory);
196
180
  this.onWorkingDirectoryChanged(e.detail.directory);
197
181
  });
198
182
  }
@@ -402,17 +386,31 @@ class CodeTree {
402
386
  .style('pointer-events', 'none');
403
387
  }
404
388
 
389
+ /**
390
+ * Clear all D3 visualization elements
391
+ */
392
+ clearD3Visualization() {
393
+ if (this.treeGroup) {
394
+ // Remove all existing nodes and links
395
+ this.treeGroup.selectAll('g.node').remove();
396
+ this.treeGroup.selectAll('path.link').remove();
397
+ }
398
+ // Reset node ID counter for proper tracking
399
+ this.nodeId = 0;
400
+ }
401
+
405
402
  /**
406
403
  * Initialize tree data structure
407
404
  */
408
405
  initializeTreeData() {
409
406
  const workingDir = this.getWorkingDirectory();
410
407
  const dirName = workingDir ? workingDir.split('/').pop() || 'Project Root' : 'Project Root';
411
- const path = workingDir || '.';
412
408
 
409
+ // Use '.' as the root path for consistency with relative path handling
410
+ // The actual working directory is retrieved via getWorkingDirectory() when needed
413
411
  this.treeData = {
414
412
  name: dirName,
415
- path: path,
413
+ path: '.', // Always use '.' for root to simplify path handling
416
414
  type: 'root',
417
415
  children: [],
418
416
  loaded: false,
@@ -470,7 +468,6 @@ class CodeTree {
470
468
  return;
471
469
  }
472
470
 
473
- console.log('Auto-discovering root level for:', workingDir);
474
471
 
475
472
  this.autoDiscovered = true;
476
473
  this.analyzing = true;
@@ -494,7 +491,7 @@ class CodeTree {
494
491
  const dirName = workingDir.split('/').pop() || 'Project Root';
495
492
  this.treeData = {
496
493
  name: dirName,
497
- path: workingDir,
494
+ path: '.', // Use '.' for root to maintain consistency with relative path handling
498
495
  type: 'root',
499
496
  children: [],
500
497
  loaded: false,
@@ -520,12 +517,7 @@ class CodeTree {
520
517
  // Get ignore patterns
521
518
  const ignorePatterns = document.getElementById('ignore-patterns')?.value || '';
522
519
 
523
- // Get show hidden files setting
524
- const showHiddenFiles = document.getElementById('show-hidden-files')?.checked || false;
525
-
526
- // Debug logging
527
- console.log('[DEBUG] Show hidden files checkbox value:', showHiddenFiles);
528
- console.log('[DEBUG] Checkbox element:', document.getElementById('show-hidden-files'));
520
+ // Enhanced debug logging
529
521
 
530
522
  // Request top-level discovery with working directory
531
523
  const requestPayload = {
@@ -533,10 +525,10 @@ class CodeTree {
533
525
  depth: 'top_level',
534
526
  languages: selectedLanguages,
535
527
  ignore_patterns: ignorePatterns,
536
- show_hidden_files: showHiddenFiles
528
+ request_id: `discover_${Date.now()}` // Add request ID for tracking
537
529
  };
538
530
 
539
- console.log('[DEBUG] Sending discovery request with payload:', requestPayload);
531
+ // Sending top-level discovery request
540
532
 
541
533
  if (this.socket) {
542
534
  this.socket.emit('code:discover:top_level', requestPayload);
@@ -637,6 +629,7 @@ class CodeTree {
637
629
  this.socket.on('code:analysis:error', (data) => this.onAnalysisError(data));
638
630
 
639
631
  // Node discovery events
632
+ this.socket.on('code:top_level:discovered', (data) => this.onTopLevelDiscovered(data));
640
633
  this.socket.on('code:directory:discovered', (data) => this.onDirectoryDiscovered(data));
641
634
  this.socket.on('code:file:discovered', (data) => this.onFileDiscovered(data));
642
635
  this.socket.on('code:file:analyzed', (data) => this.onFileAnalyzed(data));
@@ -649,27 +642,73 @@ class CodeTree {
649
642
  this.socket.on('code:directory:contents', (data) => {
650
643
  // Update the requested directory with its contents
651
644
  if (data.path) {
652
- const node = this.findNodeByPath(data.path);
645
+ // Convert absolute path back to relative path to match tree nodes
646
+ let searchPath = data.path;
647
+ const workingDir = this.getWorkingDirectory();
648
+ if (workingDir && searchPath.startsWith(workingDir)) {
649
+ // Remove working directory prefix to get relative path
650
+ searchPath = searchPath.substring(workingDir.length).replace(/^\//, '');
651
+ // If empty after removing prefix, it's the root
652
+ if (!searchPath) {
653
+ searchPath = '.';
654
+ }
655
+ }
656
+
657
+ const node = this.findNodeByPath(searchPath);
653
658
  if (node && data.children) {
654
- // Find D3 node and remove loading pulse
655
- const d3Node = this.findD3NodeByPath(data.path);
656
- if (d3Node && this.loadingNodes.has(data.path)) {
659
+ // Find D3 node and remove loading pulse (use searchPath, not data.path)
660
+ const d3Node = this.findD3NodeByPath(searchPath);
661
+ if (d3Node && this.loadingNodes.has(searchPath)) {
657
662
  this.removeLoadingPulse(d3Node);
658
663
  }
659
- node.children = data.children.map(child => ({
660
- ...child,
661
- loaded: child.type === 'directory' ? false : undefined,
662
- analyzed: child.type === 'file' ? false : undefined,
663
- expanded: false,
664
- children: []
665
- }));
664
+ node.children = data.children.map(child => {
665
+ // Construct full path for child by combining parent path with child name
666
+ // The backend now returns just the item name, not the full path
667
+ let childPath;
668
+ if (searchPath === '.' || searchPath === '') {
669
+ // Root level - child path is just the name
670
+ childPath = child.name || child.path;
671
+ } else {
672
+ // Subdirectory - combine parent path with child name
673
+ // Use child.name (backend returns just the name) or fallback to child.path
674
+ const childName = child.name || child.path;
675
+ childPath = `${searchPath}/${childName}`;
676
+ }
677
+
678
+ return {
679
+ ...child,
680
+ path: childPath, // Override with constructed path
681
+ loaded: child.type === 'directory' ? false : undefined,
682
+ analyzed: child.type === 'file' ? false : undefined,
683
+ expanded: false,
684
+ children: []
685
+ };
686
+ });
666
687
  node.loaded = true;
688
+ node.expanded = true; // Mark as expanded to show children
667
689
 
668
- // Update D3 hierarchy
690
+ // Update D3 hierarchy and make sure the node is expanded
669
691
  if (this.root && this.svg) {
692
+ // Store old root to preserve expansion state
693
+ const oldRoot = this.root;
694
+
695
+ // Recreate hierarchy with updated data
670
696
  this.root = d3.hierarchy(this.treeData);
671
697
  this.root.x0 = this.height / 2;
672
698
  this.root.y0 = 0;
699
+
700
+ // Preserve expansion state from old tree
701
+ this.preserveExpansionState(oldRoot, this.root);
702
+
703
+ // Find the D3 node again after hierarchy recreation
704
+ const updatedD3Node = this.findD3NodeByPath(searchPath);
705
+ if (updatedD3Node) {
706
+ // Ensure children are visible (not collapsed)
707
+ updatedD3Node.children = updatedD3Node._children || updatedD3Node.children;
708
+ updatedD3Node._children = null;
709
+ updatedD3Node.data.expanded = true;
710
+ }
711
+
673
712
  this.update(this.root);
674
713
  }
675
714
 
@@ -689,6 +728,7 @@ class CodeTree {
689
728
  // Top level discovery response
690
729
  this.socket.on('code:top_level:discovered', (data) => {
691
730
  if (data.items && Array.isArray(data.items)) {
731
+
692
732
  // Add discovered items to the root node
693
733
  this.treeData.children = data.items.map(item => ({
694
734
  name: item.name,
@@ -713,9 +753,14 @@ class CodeTree {
713
753
 
714
754
  // Update D3 hierarchy
715
755
  if (typeof d3 !== 'undefined') {
756
+ // Clear any existing nodes before creating new ones
757
+ this.clearD3Visualization();
758
+
759
+ // Create new hierarchy
716
760
  this.root = d3.hierarchy(this.treeData);
717
761
  this.root.x0 = this.height / 2;
718
762
  this.root.y0 = 0;
763
+
719
764
  if (this.svg) {
720
765
  this.update(this.root);
721
766
  }
@@ -758,6 +803,77 @@ class CodeTree {
758
803
  this.updateStats();
759
804
  }
760
805
 
806
+ /**
807
+ * Handle top-level discovery event (initial root directory scan)
808
+ */
809
+ onTopLevelDiscovered(data) {
810
+ // Received top-level discovery response
811
+
812
+ // Update activity ticker
813
+ this.updateActivityTicker(`📁 Discovered ${(data.items || []).length} top-level items`, 'success');
814
+
815
+ // Add to events display
816
+ this.addEventToDisplay(`📁 Found ${(data.items || []).length} top-level items in project root`, 'info');
817
+
818
+ // The root node (with path '.') should receive the children
819
+ const rootNode = this.findNodeByPath('.');
820
+
821
+ console.log('🔎 Looking for root node with path ".", found:', rootNode ? {
822
+ name: rootNode.name,
823
+ path: rootNode.path,
824
+ currentChildren: rootNode.children ? rootNode.children.length : 0
825
+ } : 'NOT FOUND');
826
+
827
+ if (rootNode && data.items) {
828
+ console.log('🌳 Populating root node with children');
829
+
830
+ // Update the root node with discovered children
831
+ rootNode.children = data.items.map(child => {
832
+ // Items at root level get their name as the path
833
+ const childPath = child.name;
834
+
835
+ console.log(` Adding child: ${child.name} with path: ${childPath}`);
836
+
837
+ return {
838
+ name: child.name,
839
+ path: childPath, // Just the name for top-level items
840
+ type: child.type,
841
+ loaded: child.type === 'directory' ? false : undefined,
842
+ analyzed: child.type === 'file' ? false : undefined,
843
+ expanded: false,
844
+ children: child.type === 'directory' ? [] : undefined,
845
+ size: child.size,
846
+ has_code: child.has_code
847
+ };
848
+ });
849
+
850
+ rootNode.loaded = true;
851
+ rootNode.expanded = true;
852
+
853
+ // Update D3 hierarchy and render
854
+ if (this.root && this.svg) {
855
+ // Recreate hierarchy with new data
856
+ this.root = d3.hierarchy(this.treeData);
857
+ this.root.x0 = this.height / 2;
858
+ this.root.y0 = 0;
859
+
860
+ // Update the tree visualization
861
+ this.update(this.root);
862
+ }
863
+
864
+ // Hide loading and show success
865
+ this.hideLoading();
866
+ this.updateBreadcrumb(`Discovered ${data.items.length} items`, 'success');
867
+ this.showNotification(`Found ${data.items.length} top-level items`, 'success');
868
+ } else {
869
+ console.error('❌ Could not find root node to populate');
870
+ this.showNotification('Failed to populate root directory', 'error');
871
+ }
872
+
873
+ // Mark analysis as complete
874
+ this.analyzing = false;
875
+ }
876
+
761
877
  /**
762
878
  * Handle directory discovered event
763
879
  */
@@ -768,48 +884,106 @@ class CodeTree {
768
884
  // Add to events display
769
885
  this.addEventToDisplay(`📁 Found ${(data.children || []).length} items in: ${data.name || data.path}`, 'info');
770
886
 
887
+ console.log('📥 Received directory discovery:', {
888
+ path: data.path,
889
+ name: data.name,
890
+ childrenCount: (data.children || []).length,
891
+ children: (data.children || []).map(c => ({ name: c.name, type: c.type })),
892
+ workingDir: this.getWorkingDirectory()
893
+ });
894
+
895
+ // Convert absolute path back to relative path to match tree nodes
896
+ let searchPath = data.path;
897
+ const workingDir = this.getWorkingDirectory();
898
+ if (workingDir && searchPath.startsWith(workingDir)) {
899
+ // Remove working directory prefix to get relative path
900
+ searchPath = searchPath.substring(workingDir.length).replace(/^\//, '');
901
+ // If empty after removing prefix, it's the root
902
+ if (!searchPath) {
903
+ searchPath = '.';
904
+ }
905
+ }
906
+
907
+ console.log('🔎 Searching for node with path:', searchPath);
908
+
771
909
  // Find the node that was clicked to trigger this discovery
772
- const node = this.findNodeByPath(data.path);
910
+ const node = this.findNodeByPath(searchPath);
911
+
912
+ // Located target node for expansion
913
+
773
914
  if (node && data.children) {
774
915
  // Update the node with discovered children
775
- node.children = data.children.map(child => ({
776
- name: child.name,
777
- path: child.path,
778
- type: child.type,
779
- loaded: child.type === 'directory' ? false : undefined,
780
- analyzed: child.type === 'file' ? false : undefined,
781
- expanded: false,
782
- children: child.type === 'directory' ? [] : undefined,
783
- size: child.size,
784
- has_code: child.has_code
785
- }));
916
+ node.children = data.children.map(child => {
917
+ // Construct full path for child by combining parent path with child name
918
+ // The backend now returns just the item name, not the full path
919
+ let childPath;
920
+ if (searchPath === '.' || searchPath === '') {
921
+ // Root level - child path is just the name
922
+ childPath = child.name || child.path;
923
+ } else {
924
+ // Subdirectory - combine parent path with child name
925
+ // Use child.name (backend returns just the name) or fallback to child.path
926
+ const childName = child.name || child.path;
927
+ childPath = `${searchPath}/${childName}`;
928
+ }
929
+
930
+ return {
931
+ name: child.name,
932
+ path: childPath, // Use constructed path instead of child.path
933
+ type: child.type,
934
+ loaded: child.type === 'directory' ? false : undefined,
935
+ analyzed: child.type === 'file' ? false : undefined,
936
+ expanded: false,
937
+ children: child.type === 'directory' ? [] : undefined,
938
+ size: child.size,
939
+ has_code: child.has_code
940
+ };
941
+ });
786
942
  node.loaded = true;
787
943
  node.expanded = true;
788
944
 
789
- // Find D3 node and remove loading pulse
790
- const d3Node = this.findD3NodeByPath(data.path);
945
+ // Find D3 node and remove loading pulse (use searchPath, not data.path)
946
+ const d3Node = this.findD3NodeByPath(searchPath);
791
947
  if (d3Node) {
792
948
  // Remove loading animation
793
- if (this.loadingNodes.has(data.path)) {
949
+ if (this.loadingNodes.has(searchPath)) {
794
950
  this.removeLoadingPulse(d3Node);
795
951
  }
796
-
797
- // Expand the node in D3
798
- if (d3Node.data) {
799
- d3Node.data.children = node.children;
800
- d3Node._children = null;
801
- }
802
952
  }
803
953
 
804
- // Update D3 hierarchy and redraw
954
+ // Update D3 hierarchy and redraw with expanded node
805
955
  if (this.root && this.svg) {
956
+ // Store old root to preserve expansion state
957
+ const oldRoot = this.root;
958
+
959
+ // Recreate hierarchy with updated data
806
960
  this.root = d3.hierarchy(this.treeData);
961
+
962
+ // Restore positions for smooth animation
963
+ this.root.x0 = this.height / 2;
964
+ this.root.y0 = 0;
965
+
966
+ // Preserve expansion state from old tree
967
+ this.preserveExpansionState(oldRoot, this.root);
968
+
969
+ // Find the D3 node again after hierarchy recreation
970
+ const updatedD3Node = this.findD3NodeByPath(searchPath);
971
+ if (updatedD3Node && updatedD3Node.data.children && updatedD3Node.data.children.length > 0) {
972
+ // Ensure the node is expanded to show children
973
+ updatedD3Node.children = updatedD3Node._children || updatedD3Node.children;
974
+ updatedD3Node._children = null;
975
+ // Mark data as expanded
976
+ updatedD3Node.data.expanded = true;
977
+ }
978
+
807
979
  this.update(this.root);
808
980
  }
809
981
 
810
982
  this.updateBreadcrumb(`Loaded ${node.children.length} items from ${node.name}`, 'success');
811
983
  this.updateStats();
812
984
  } else if (!node) {
985
+ this.logAllPaths(this.treeData);
986
+ } else if (node && !data.children) {
813
987
  // This might be a top-level directory discovery
814
988
  const pathParts = data.path ? data.path.split('/').filter(p => p) : [];
815
989
  const isTopLevel = pathParts.length === 1;
@@ -1069,7 +1243,6 @@ class CodeTree {
1069
1243
  */
1070
1244
  onInfoEvent(data) {
1071
1245
  // Log to console for debugging
1072
- console.log('[INFO]', data.type, data.message);
1073
1246
 
1074
1247
  // Update breadcrumb for certain events
1075
1248
  if (data.type && data.type.startsWith('discovery.')) {
@@ -1080,7 +1253,6 @@ class CodeTree {
1080
1253
  this.updateBreadcrumb(data.message, 'success');
1081
1254
  // Show stats if available
1082
1255
  if (data.stats) {
1083
- console.log('[DISCOVERY STATS]', data.stats);
1084
1256
  }
1085
1257
  } else if (data.type === 'discovery.directory' || data.type === 'discovery.file') {
1086
1258
  // Quick flash of discovery events
@@ -1095,7 +1267,6 @@ class CodeTree {
1095
1267
  // Show stats if available
1096
1268
  if (data.stats) {
1097
1269
  const statsMsg = `Found: ${data.stats.classes || 0} classes, ${data.stats.functions || 0} functions, ${data.stats.methods || 0} methods`;
1098
- console.log('[ANALYSIS STATS]', statsMsg);
1099
1270
  }
1100
1271
  } else if (data.type === 'analysis.class' || data.type === 'analysis.function' || data.type === 'analysis.method') {
1101
1272
  // Show found elements briefly
@@ -1152,7 +1323,6 @@ class CodeTree {
1152
1323
  }
1153
1324
 
1154
1325
  // Could update a UI element here if we had an event log display
1155
- console.log('[EVENT LOG]', data.type, data.message);
1156
1326
  }
1157
1327
 
1158
1328
  /**
@@ -1278,6 +1448,8 @@ class CodeTree {
1278
1448
  findNodeByPath(path, node = null) {
1279
1449
  if (!node) {
1280
1450
  node = this.treeData;
1451
+ // Searching for node by path
1452
+ // Root node identified
1281
1453
  }
1282
1454
 
1283
1455
  if (node.path === path) {
@@ -1293,9 +1465,24 @@ class CodeTree {
1293
1465
  }
1294
1466
  }
1295
1467
 
1468
+ if (!node.parent) {
1469
+ // Node path not found in current tree structure
1470
+ }
1296
1471
  return null;
1297
1472
  }
1298
1473
 
1474
+ /**
1475
+ * Helper to log all paths in tree for debugging
1476
+ */
1477
+ logAllPaths(node, indent = '') {
1478
+ console.log(`${indent}${node.path} (${node.name})`);
1479
+ if (node.children) {
1480
+ for (const child of node.children) {
1481
+ this.logAllPaths(child, indent + ' ');
1482
+ }
1483
+ }
1484
+ }
1485
+
1299
1486
  /**
1300
1487
  * Find D3 hierarchy node by path
1301
1488
  */
@@ -1303,6 +1490,30 @@ class CodeTree {
1303
1490
  if (!this.root) return null;
1304
1491
  return this.root.descendants().find(d => d.data.path === path);
1305
1492
  }
1493
+
1494
+ /**
1495
+ * Preserve expansion state when recreating hierarchy
1496
+ */
1497
+ preserveExpansionState(oldRoot, newRoot) {
1498
+ if (!oldRoot || !newRoot) return;
1499
+
1500
+ // Create a map of expanded nodes from the old tree
1501
+ const expansionMap = new Map();
1502
+ oldRoot.descendants().forEach(node => {
1503
+ if (node.data.expanded || (node.children && !node._children)) {
1504
+ expansionMap.set(node.data.path, true);
1505
+ }
1506
+ });
1507
+
1508
+ // Apply expansion state to new tree
1509
+ newRoot.descendants().forEach(node => {
1510
+ if (expansionMap.has(node.data.path)) {
1511
+ node.children = node._children || node.children;
1512
+ node._children = null;
1513
+ node.data.expanded = true;
1514
+ }
1515
+ });
1516
+ }
1306
1517
 
1307
1518
  /**
1308
1519
  * Update statistics display
@@ -1655,9 +1866,18 @@ class CodeTree {
1655
1866
  centerOnNode(d) {
1656
1867
  if (!this.svg || !this.zoom) return;
1657
1868
 
1658
- const transform = d3.zoomTransform(this.svg.node());
1659
- const x = -d.y * transform.k + this.width / 2;
1660
- const y = -d.x * transform.k + this.height / 2;
1869
+ // Get current transform or use default zoom level
1870
+ const currentTransform = d3.zoomTransform(this.svg.node());
1871
+ // Zoom in to 2x for better focus on clicked node
1872
+ const targetScale = currentTransform.k < 2 ? 2 : currentTransform.k;
1873
+
1874
+ // Account for the initial tree group offset
1875
+ const initialOffsetX = this.margin.left + 100;
1876
+ const initialOffsetY = this.height / 2;
1877
+
1878
+ // Calculate position to center the node accounting for the tree group's transform
1879
+ const x = initialOffsetX - d.y * targetScale + this.width / 2;
1880
+ const y = initialOffsetY - d.x * targetScale + this.height / 2;
1661
1881
 
1662
1882
  this.svg.transition()
1663
1883
  .duration(750)
@@ -1665,7 +1885,7 @@ class CodeTree {
1665
1885
  this.zoom.transform,
1666
1886
  d3.zoomIdentity
1667
1887
  .translate(x, y)
1668
- .scale(transform.k)
1888
+ .scale(targetScale)
1669
1889
  );
1670
1890
  }
1671
1891
 
@@ -1678,23 +1898,29 @@ class CodeTree {
1678
1898
  // Use the same radialPoint function for consistency
1679
1899
  const [x, y] = this.radialPoint(d.x, d.y);
1680
1900
 
1681
- // Get current transform
1682
- const transform = d3.zoomTransform(this.svg.node());
1901
+ // Get current transform or use default zoom level
1902
+ const currentTransform = d3.zoomTransform(this.svg.node());
1903
+ // Zoom in to 2x for better focus on clicked node
1904
+ const targetScale = currentTransform.k < 2 ? 2 : currentTransform.k;
1905
+
1906
+ // Account for the initial tree group centering
1907
+ const centerX = this.width / 2;
1908
+ const centerY = this.height / 2;
1683
1909
 
1684
1910
  // Calculate translation to center the node
1685
- // The tree is already centered at width/2, height/2 via transform
1686
- // So we need to adjust relative to that center
1687
- const targetX = this.width / 2 - x * transform.k;
1688
- const targetY = this.height / 2 - y * transform.k;
1911
+ // The tree group is centered at centerX, centerY
1912
+ // We need to offset by the node's position scaled
1913
+ const targetX = centerX - x * targetScale;
1914
+ const targetY = centerY - y * targetScale;
1689
1915
 
1690
- // Apply smooth transition to center the node
1916
+ // Apply smooth transition to center the node with zoom
1691
1917
  this.svg.transition()
1692
1918
  .duration(750)
1693
1919
  .call(
1694
1920
  this.zoom.transform,
1695
1921
  d3.zoomIdentity
1696
1922
  .translate(targetX, targetY)
1697
- .scale(transform.k)
1923
+ .scale(targetScale)
1698
1924
  );
1699
1925
  }
1700
1926
 
@@ -1703,26 +1929,50 @@ class CodeTree {
1703
1929
  */
1704
1930
  highlightActiveNode(d) {
1705
1931
  // Reset all nodes to normal size and clear parent context
1706
- this.treeGroup.selectAll('circle.node-circle')
1932
+ // First clear classes on the selection
1933
+ const allCircles = this.treeGroup.selectAll('circle.node-circle');
1934
+ allCircles
1935
+ .classed('active', false)
1936
+ .classed('parent-context', false);
1937
+
1938
+ // Then apply transition separately
1939
+ allCircles
1707
1940
  .transition()
1708
1941
  .duration(300)
1709
1942
  .attr('r', 8)
1710
- .classed('active', false)
1711
- .classed('parent-context', false)
1712
1943
  .style('stroke', null)
1713
1944
  .style('stroke-width', null)
1714
1945
  .style('opacity', null);
1715
1946
 
1947
+ // Reset all labels to normal
1948
+ this.treeGroup.selectAll('text.node-label')
1949
+ .style('font-weight', 'normal')
1950
+ .style('font-size', '12px');
1951
+
1716
1952
  // Find and increase size of clicked node - use data matching
1717
- this.treeGroup.selectAll('g.node')
1953
+ // Make the size increase MUCH more dramatic: 8 -> 20 (2.5x the size)
1954
+ const activeNodeCircle = this.treeGroup.selectAll('g.node')
1718
1955
  .filter(node => node === d)
1719
- .select('circle.node-circle')
1956
+ .select('circle.node-circle');
1957
+
1958
+ // First set the class (not part of transition)
1959
+ activeNodeCircle.classed('active', true);
1960
+
1961
+ // Then apply the transition with styles - MUCH LARGER
1962
+ activeNodeCircle
1720
1963
  .transition()
1721
1964
  .duration(300)
1722
- .attr('r', 12) // Larger radius
1723
- .classed('active', true)
1965
+ .attr('r', 20) // Much larger radius (2.5x)
1724
1966
  .style('stroke', '#3b82f6')
1725
- .style('stroke-width', 3);
1967
+ .style('stroke-width', 5) // Thicker border
1968
+ .style('filter', 'drop-shadow(0 0 15px rgba(59, 130, 246, 0.6))'); // Stronger glow effect
1969
+
1970
+ // Also make the label bold
1971
+ this.treeGroup.selectAll('g.node')
1972
+ .filter(node => node === d)
1973
+ .select('text.node-label')
1974
+ .style('font-weight', 'bold')
1975
+ .style('font-size', '14px'); // Slightly larger text
1726
1976
 
1727
1977
  // Store active node
1728
1978
  this.activeNode = d;
@@ -1740,9 +1990,9 @@ class CodeTree {
1740
1990
  // Add to loading set
1741
1991
  this.loadingNodes.add(d.data.path);
1742
1992
 
1743
- // Add pulsing class and orange color
1744
- node.classed('loading-pulse', true)
1745
- .style('fill', '#fb923c'); // Orange color for loading
1993
+ // Add pulsing class and orange color - separate operations
1994
+ node.classed('loading-pulse', true);
1995
+ node.style('fill', '#fb923c'); // Orange color for loading
1746
1996
 
1747
1997
  // Create pulse animation
1748
1998
  const pulseAnimation = () => {
@@ -1778,11 +2028,14 @@ class CodeTree {
1778
2028
  .filter(node => node === d)
1779
2029
  .select('circle.node-circle');
1780
2030
 
1781
- node.classed('loading-pulse', false)
1782
- .interrupt() // Stop animation
2031
+ // Clear class first
2032
+ node.classed('loading-pulse', false);
2033
+
2034
+ // Then interrupt and transition
2035
+ node.interrupt() // Stop animation
1783
2036
  .transition()
1784
2037
  .duration(300)
1785
- .attr('r', this.activeNode === d ? 12 : 8)
2038
+ .attr('r', this.activeNode === d ? 20 : 8) // Use 20 for active node
1786
2039
  .style('opacity', 1)
1787
2040
  .style('fill', d => this.getNodeColor(d)); // Restore original color
1788
2041
  }
@@ -1797,9 +2050,10 @@ class CodeTree {
1797
2050
  const parentNode = this.treeGroup.selectAll('g.node')
1798
2051
  .filter(node => node === d.parent);
1799
2052
 
1800
- // Highlight parent with different style
1801
- parentNode.select('circle.node-circle')
1802
- .classed('parent-context', true)
2053
+ // Highlight parent with different style - separate class from styles
2054
+ const parentCircle = parentNode.select('circle.node-circle');
2055
+ parentCircle.classed('parent-context', true);
2056
+ parentCircle
1803
2057
  .style('stroke', '#10b981')
1804
2058
  .style('stroke-width', 3)
1805
2059
  .style('opacity', 0.8);
@@ -1847,56 +2101,146 @@ class CodeTree {
1847
2101
  * Handle node click - implement lazy loading with enhanced visual feedback
1848
2102
  */
1849
2103
  onNodeClick(event, d) {
1850
- event.stopPropagation();
2104
+ // Handle node click interaction
1851
2105
 
1852
- // Center on clicked node
1853
- if (this.isRadialLayout) {
1854
- this.centerOnNodeRadial(d);
2106
+ // Check event parameter
2107
+ if (event) {
2108
+ try {
2109
+ if (typeof event.stopPropagation === 'function') {
2110
+ event.stopPropagation();
2111
+ } else {
2112
+ }
2113
+ } catch (error) {
2114
+ console.error('[CodeTree] ERROR calling stopPropagation:', error);
2115
+ }
1855
2116
  } else {
1856
- this.centerOnNode(d);
1857
2117
  }
1858
2118
 
1859
- // Highlight with larger icon
1860
- this.highlightActiveNode(d);
2119
+ // Check d parameter structure
2120
+ if (!d) {
2121
+ console.error('[CodeTree] ERROR: d is null/undefined, cannot continue');
2122
+ return;
2123
+ }
2124
+
2125
+ if (!d.data) {
2126
+ console.error('[CodeTree] ERROR: d.data is null/undefined, cannot continue');
2127
+ return;
2128
+ }
2129
+
2130
+ // Node interaction detected
2131
+
2132
+ // === PHASE 1: Immediate Visual Effects (Synchronous) ===
2133
+ // These execute immediately before any async operations
2134
+
2135
+
2136
+ // Center on clicked node (immediate visual effect)
2137
+ try {
2138
+ if (this.isRadialLayout) {
2139
+ if (typeof this.centerOnNodeRadial === 'function') {
2140
+ this.centerOnNodeRadial(d);
2141
+ } else {
2142
+ console.error('[CodeTree] centerOnNodeRadial is not a function!');
2143
+ }
2144
+ } else {
2145
+ if (typeof this.centerOnNode === 'function') {
2146
+ this.centerOnNode(d);
2147
+ } else {
2148
+ console.error('[CodeTree] centerOnNode is not a function!');
2149
+ }
2150
+ }
2151
+ } catch (error) {
2152
+ console.error('[CodeTree] ERROR during centering:', error, error.stack);
2153
+ }
2154
+
2155
+
2156
+ // Highlight with larger icon (immediate visual effect)
2157
+ try {
2158
+ if (typeof this.highlightActiveNode === 'function') {
2159
+ this.highlightActiveNode(d);
2160
+ } else {
2161
+ console.error('[CodeTree] highlightActiveNode is not a function!');
2162
+ }
2163
+ } catch (error) {
2164
+ console.error('[CodeTree] ERROR during highlightActiveNode:', error, error.stack);
2165
+ }
2166
+
2167
+
2168
+ // Show parent context (immediate visual effect)
2169
+ try {
2170
+ if (typeof this.showWithParent === 'function') {
2171
+ this.showWithParent(d);
2172
+ } else {
2173
+ console.error('[CodeTree] showWithParent is not a function!');
2174
+ }
2175
+ } catch (error) {
2176
+ console.error('[CodeTree] ERROR during showWithParent:', error, error.stack);
2177
+ }
2178
+
2179
+
2180
+ // Add pulsing animation immediately for directories
2181
+
2182
+ if (d.data.type === 'directory' && !d.data.loaded) {
2183
+ try {
2184
+ if (typeof this.addLoadingPulse === 'function') {
2185
+ this.addLoadingPulse(d);
2186
+ } else {
2187
+ console.error('[CodeTree] addLoadingPulse is not a function!');
2188
+ }
2189
+ } catch (error) {
2190
+ console.error('[CodeTree] ERROR during addLoadingPulse:', error, error.stack);
2191
+ }
2192
+ } else {
2193
+ }
2194
+
2195
+
2196
+ // === PHASE 2: Prepare Data (Synchronous) ===
1861
2197
 
1862
- // Show parent context
1863
- this.showWithParent(d);
1864
2198
 
1865
2199
  // Get selected languages from checkboxes
1866
2200
  const selectedLanguages = [];
1867
- document.querySelectorAll('.language-checkbox:checked').forEach(cb => {
2201
+ const checkboxes = document.querySelectorAll('.language-checkbox:checked');
2202
+ checkboxes.forEach(cb => {
1868
2203
  selectedLanguages.push(cb.value);
1869
2204
  });
1870
2205
 
1871
2206
  // Get ignore patterns
1872
- const ignorePatterns = document.getElementById('ignore-patterns')?.value || '';
2207
+ const ignorePatternsElement = document.getElementById('ignore-patterns');
2208
+ const ignorePatterns = ignorePatternsElement?.value || '';
1873
2209
 
1874
- // Get show hidden files setting
1875
- const showHiddenFiles = document.getElementById('show-hidden-files')?.checked || false;
2210
+
2211
+ // === PHASE 3: Async Operations (Delayed) ===
2212
+ // Add a small delay to ensure visual effects are rendered first
1876
2213
 
1877
2214
  // For directories that haven't been loaded yet, request discovery
1878
2215
  if (d.data.type === 'directory' && !d.data.loaded) {
1879
- // Add pulsing animation
1880
- this.addLoadingPulse(d);
2216
+ // Mark as loading immediately to prevent duplicate requests
2217
+ d.data.loaded = 'loading';
1881
2218
 
1882
2219
  // Ensure path is absolute or relative to working directory
1883
2220
  const fullPath = this.ensureFullPath(d.data.path);
1884
2221
 
1885
- // Request directory contents via Socket.IO
1886
- if (this.socket) {
1887
- this.socket.emit('code:discover:directory', {
1888
- path: fullPath,
1889
- depth: 1, // Only get immediate children
1890
- languages: selectedLanguages,
1891
- ignore_patterns: ignorePatterns,
1892
- show_hidden_files: showHiddenFiles
1893
- });
2222
+ // Sending discovery request for child content
2223
+
2224
+ // Store reference to the D3 node for later expansion
2225
+ const clickedD3Node = d;
2226
+
2227
+ // Delay the socket request to ensure visual effects are rendered
2228
+ setTimeout(() => {
1894
2229
 
1895
- // Mark as loading to prevent duplicate requests
1896
- d.data.loaded = 'loading';
1897
- this.updateBreadcrumb(`Loading ${d.data.name}...`, 'info');
1898
- this.showNotification(`Loading directory: ${d.data.name}`, 'info');
1899
- }
2230
+ // Request directory contents via Socket.IO
2231
+ if (this.socket) {
2232
+ this.socket.emit('code:discover:directory', {
2233
+ path: fullPath,
2234
+ depth: 1, // Only get immediate children
2235
+ languages: selectedLanguages,
2236
+ ignore_patterns: ignorePatterns
2237
+ });
2238
+
2239
+ this.updateBreadcrumb(`Loading ${d.data.name}...`, 'info');
2240
+ this.showNotification(`Loading directory: ${d.data.name}`, 'info');
2241
+ } else {
2242
+ }
2243
+ }, 100); // 100ms delay to ensure visual effects render first
1900
2244
  }
1901
2245
  // For files that haven't been analyzed, request analysis
1902
2246
  else if (d.data.type === 'file' && !d.data.analyzed) {
@@ -1907,28 +2251,54 @@ class CodeTree {
1907
2251
  return;
1908
2252
  }
1909
2253
 
1910
- // Add pulsing animation
2254
+ // Add pulsing animation immediately
1911
2255
  this.addLoadingPulse(d);
1912
2256
 
2257
+ // Mark as loading immediately
2258
+ d.data.analyzed = 'loading';
2259
+
1913
2260
  // Ensure path is absolute or relative to working directory
1914
2261
  const fullPath = this.ensureFullPath(d.data.path);
1915
2262
 
1916
- // Get current show_hidden_files setting
1917
- const showHiddenFilesCheckbox = document.getElementById('show-hidden-files');
1918
- const showHiddenFiles = showHiddenFilesCheckbox ? showHiddenFilesCheckbox.checked : false;
1919
-
1920
- if (this.socket) {
1921
- this.socket.emit('code:analyze:file', {
1922
- path: fullPath,
1923
- show_hidden_files: showHiddenFiles
1924
- });
2263
+ // Delay the socket request to ensure visual effects are rendered
2264
+ setTimeout(() => {
1925
2265
 
1926
- d.data.analyzed = 'loading';
1927
- this.updateBreadcrumb(`Analyzing ${d.data.name}...`, 'info');
1928
- this.showNotification(`Analyzing: ${d.data.name}`, 'info');
1929
- }
2266
+ if (this.socket) {
2267
+ this.socket.emit('code:analyze:file', {
2268
+ path: fullPath
2269
+ });
2270
+
2271
+ this.updateBreadcrumb(`Analyzing ${d.data.name}...`, 'info');
2272
+ this.showNotification(`Analyzing: ${d.data.name}`, 'info');
2273
+ }
2274
+ }, 100); // 100ms delay to ensure visual effects render first
1930
2275
  }
1931
2276
  // Toggle children visibility for already loaded nodes
2277
+ else if (d.data.type === 'directory' && d.data.loaded === true) {
2278
+ // Directory is loaded, toggle expansion
2279
+ if (d.children) {
2280
+ // Collapse - hide children
2281
+ d._children = d.children;
2282
+ d.children = null;
2283
+ d.data.expanded = false;
2284
+ } else if (d._children) {
2285
+ // Expand - show children
2286
+ d.children = d._children;
2287
+ d._children = null;
2288
+ d.data.expanded = true;
2289
+ } else if (d.data.children && d.data.children.length > 0) {
2290
+ // Children exist in data but not in D3 node, recreate hierarchy
2291
+ this.root = d3.hierarchy(this.treeData);
2292
+ const updatedD3Node = this.findD3NodeByPath(d.data.path);
2293
+ if (updatedD3Node) {
2294
+ updatedD3Node.children = updatedD3Node._children || updatedD3Node.children;
2295
+ updatedD3Node._children = null;
2296
+ updatedD3Node.data.expanded = true;
2297
+ }
2298
+ }
2299
+ this.update(this.root);
2300
+ }
2301
+ // Also handle other nodes that might have children
1932
2302
  else if (d.children || d._children) {
1933
2303
  if (d.children) {
1934
2304
  d._children = d.children;
@@ -1940,37 +2310,58 @@ class CodeTree {
1940
2310
  d.data.expanded = true;
1941
2311
  }
1942
2312
  this.update(d);
2313
+ } else {
1943
2314
  }
1944
2315
 
1945
2316
  // Update selection
1946
2317
  this.selectedNode = d;
1947
- this.highlightNode(d);
2318
+ try {
2319
+ this.highlightNode(d);
2320
+ } catch (error) {
2321
+ console.error('[CodeTree] ERROR during highlightNode:', error);
2322
+ }
2323
+
1948
2324
  }
1949
2325
 
1950
2326
  /**
1951
2327
  * Ensure path is absolute or relative to working directory
1952
2328
  */
1953
2329
  ensureFullPath(path) {
2330
+ console.log('🔗 ensureFullPath called with:', path);
2331
+
1954
2332
  if (!path) return path;
1955
2333
 
1956
2334
  // If already absolute, return as is
1957
2335
  if (path.startsWith('/')) {
2336
+ console.log(' → Already absolute, returning:', path);
1958
2337
  return path;
1959
2338
  }
1960
2339
 
1961
2340
  // Get working directory
1962
2341
  const workingDir = this.getWorkingDirectory();
2342
+ console.log(' → Working directory:', workingDir);
2343
+
1963
2344
  if (!workingDir) {
2345
+ console.log(' → No working directory, returning original:', path);
1964
2346
  return path;
1965
2347
  }
1966
2348
 
1967
- // If path is relative, make it relative to working directory
1968
- if (path === '.' || path === workingDir) {
2349
+ // Special handling for root path
2350
+ if (path === '.') {
2351
+ console.log(' → Root path detected, returning working dir:', workingDir);
2352
+ return workingDir;
2353
+ }
2354
+
2355
+ // If path equals working directory, return as is
2356
+ if (path === workingDir) {
2357
+ console.log(' → Path equals working directory, returning:', workingDir);
1969
2358
  return workingDir;
1970
2359
  }
1971
2360
 
1972
2361
  // Combine working directory with relative path
1973
- return `${workingDir}/${path}`.replace(/\/+/g, '/');
2362
+ const result = `${workingDir}/${path}`.replace(/\/+/g, '/');
2363
+ console.log(' → Combining with working dir, result:', result);
2364
+ return result;
1974
2365
  }
1975
2366
 
1976
2367
  /**
@@ -2532,4 +2923,4 @@ document.addEventListener('DOMContentLoaded', () => {
2532
2923
  }
2533
2924
  });
2534
2925
  }
2535
- });
2926
+ });/* Cache buster: 1756393851 */