claude-mpm 4.1.10__py3-none-any.whl → 4.1.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 (48) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/cli/__init__.py +11 -0
  3. claude_mpm/cli/commands/analyze.py +2 -1
  4. claude_mpm/cli/commands/configure.py +9 -8
  5. claude_mpm/cli/commands/configure_tui.py +3 -1
  6. claude_mpm/cli/commands/dashboard.py +288 -0
  7. claude_mpm/cli/commands/debug.py +0 -1
  8. claude_mpm/cli/commands/mpm_init.py +427 -0
  9. claude_mpm/cli/commands/mpm_init_handler.py +83 -0
  10. claude_mpm/cli/parsers/base_parser.py +15 -0
  11. claude_mpm/cli/parsers/dashboard_parser.py +113 -0
  12. claude_mpm/cli/parsers/mpm_init_parser.py +122 -0
  13. claude_mpm/constants.py +10 -0
  14. claude_mpm/dashboard/analysis_runner.py +52 -25
  15. claude_mpm/dashboard/static/built/components/activity-tree.js +1 -1
  16. claude_mpm/dashboard/static/built/components/code-tree.js +2 -0
  17. claude_mpm/dashboard/static/built/components/code-viewer.js +2 -0
  18. claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
  19. claude_mpm/dashboard/static/built/dashboard.js +1 -1
  20. claude_mpm/dashboard/static/built/socket-client.js +1 -1
  21. claude_mpm/dashboard/static/css/code-tree.css +330 -1
  22. claude_mpm/dashboard/static/dist/components/activity-tree.js +1 -1
  23. claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
  24. claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
  25. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  26. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  27. claude_mpm/dashboard/static/js/components/activity-tree.js +212 -13
  28. claude_mpm/dashboard/static/js/components/code-tree.js +1999 -821
  29. claude_mpm/dashboard/static/js/components/event-viewer.js +58 -19
  30. claude_mpm/dashboard/static/js/dashboard.js +15 -3
  31. claude_mpm/dashboard/static/js/socket-client.js +74 -32
  32. claude_mpm/dashboard/templates/index.html +9 -11
  33. claude_mpm/services/agents/memory/memory_format_service.py +3 -1
  34. claude_mpm/services/cli/agent_cleanup_service.py +1 -4
  35. claude_mpm/services/cli/startup_checker.py +0 -1
  36. claude_mpm/services/core/cache_manager.py +0 -1
  37. claude_mpm/services/socketio/event_normalizer.py +64 -0
  38. claude_mpm/services/socketio/handlers/code_analysis.py +502 -0
  39. claude_mpm/services/socketio/server/connection_manager.py +3 -1
  40. claude_mpm/tools/code_tree_analyzer.py +843 -25
  41. claude_mpm/tools/code_tree_builder.py +0 -1
  42. claude_mpm/tools/code_tree_events.py +113 -15
  43. {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.11.dist-info}/METADATA +2 -1
  44. {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.11.dist-info}/RECORD +48 -41
  45. {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.11.dist-info}/WHEEL +0 -0
  46. {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.11.dist-info}/entry_points.txt +0 -0
  47. {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.11.dist-info}/licenses/LICENSE +0 -0
  48. {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.11.dist-info}/top_level.txt +0 -0
@@ -39,7 +39,6 @@ class EventViewer {
39
39
 
40
40
  // Subscribe to socket events
41
41
  this.socketClient.onEventUpdate((events, sessions) => {
42
- console.log('EventViewer received event update:', events?.length || 0, 'events');
43
42
  // Ensure we always have a valid events array
44
43
  this.events = Array.isArray(events) ? events : [];
45
44
  this.updateDisplay();
@@ -76,7 +75,6 @@ class EventViewer {
76
75
  setupKeyboardNavigation() {
77
76
  // Keyboard navigation is now handled by Dashboard.setupUnifiedKeyboardNavigation()
78
77
  // This method is kept for backward compatibility but does nothing
79
- console.log('EventViewer: Keyboard navigation handled by unified Dashboard system');
80
78
  }
81
79
 
82
80
  /**
@@ -110,18 +108,10 @@ class EventViewer {
110
108
  }
111
109
 
112
110
  this.filteredEvents = this.events.filter(event => {
113
- // Filter out info level log messages
114
- if (event.type === 'log' && event.data && event.data.level === 'info') {
115
- return false;
116
- }
117
-
118
- // Filter out code analysis events (they're shown in footer status bar)
119
- if (event.type === 'code' ||
120
- (event.type === 'unknown' && event.originalEventName && event.originalEventName.startsWith('code:'))) {
121
- return false;
122
- }
111
+ // NO AUTOMATIC FILTERING - All events are shown by default for complete visibility
112
+ // Users can apply their own filters using the search and type filter controls
123
113
 
124
- // Search filter
114
+ // User-controlled search filter
125
115
  if (this.searchFilter) {
126
116
  const searchableText = [
127
117
  event.type || '',
@@ -134,7 +124,7 @@ class EventViewer {
134
124
  }
135
125
  }
136
126
 
137
- // Type filter - now handles full hook types (like "hook.user_prompt") and main types
127
+ // User-controlled type filter - handles full hook types (like "hook.user_prompt") and main types
138
128
  if (this.typeFilter) {
139
129
  // Use the same logic as formatEventType to get the full event type
140
130
  const eventType = event.type && event.type.trim() !== '' ? event.type : '';
@@ -144,13 +134,14 @@ class EventViewer {
144
134
  }
145
135
  }
146
136
 
147
- // Session filter
137
+ // User-controlled session filter
148
138
  if (this.sessionFilter && this.sessionFilter !== '') {
149
139
  if (!event.data || event.data.session_id !== this.sessionFilter) {
150
140
  return false;
151
141
  }
152
142
  }
153
143
 
144
+ // Allow all events through unless filtered by user controls
154
145
  return true;
155
146
  });
156
147
 
@@ -222,7 +213,6 @@ class EventViewer {
222
213
  * Update the display with current events
223
214
  */
224
215
  updateDisplay() {
225
- console.log('EventViewer updating display with', this.events?.length || 0, 'events');
226
216
  this.updateEventTypeDropdown();
227
217
  this.applyFilters();
228
218
  }
@@ -234,6 +224,9 @@ class EventViewer {
234
224
  const eventsList = document.getElementById('events-list');
235
225
  if (!eventsList) return;
236
226
 
227
+ // Check if user is at bottom BEFORE rendering (for autoscroll decision)
228
+ const wasAtBottom = (eventsList.scrollTop + eventsList.clientHeight >= eventsList.scrollHeight - 10);
229
+
237
230
  if (this.filteredEvents.length === 0) {
238
231
  eventsList.innerHTML = `
239
232
  <div class="no-events">
@@ -281,9 +274,12 @@ class EventViewer {
281
274
  window.dashboard.tabNavigation.events.items = this.filteredEventElements;
282
275
  }
283
276
 
284
- // Auto-scroll to bottom if enabled
285
- if (this.autoScroll && this.filteredEvents.length > 0) {
286
- eventsList.scrollTop = eventsList.scrollHeight;
277
+ // Auto-scroll only if user was already at bottom before rendering
278
+ if (this.filteredEvents.length > 0 && wasAtBottom && this.autoScroll) {
279
+ // Use requestAnimationFrame to ensure DOM has updated
280
+ requestAnimationFrame(() => {
281
+ eventsList.scrollTop = eventsList.scrollHeight;
282
+ });
287
283
  }
288
284
  }
289
285
 
@@ -337,6 +333,8 @@ class EventViewer {
337
333
  return this.formatMemoryEvent(event);
338
334
  case 'log':
339
335
  return this.formatLogEvent(event);
336
+ case 'code':
337
+ return this.formatCodeEvent(event);
340
338
  default:
341
339
  return this.formatGenericEvent(event);
342
340
  }
@@ -480,6 +478,47 @@ class EventViewer {
480
478
  return `<strong>[${level.toUpperCase()}]</strong> ${truncated}`;
481
479
  }
482
480
 
481
+ /**
482
+ * Format code analysis event data
483
+ */
484
+ formatCodeEvent(event) {
485
+ const data = event.data || {};
486
+
487
+ // Handle different code event subtypes
488
+ if (event.subtype === 'progress') {
489
+ const message = data.message || 'Processing...';
490
+ const percentage = data.percentage;
491
+ if (percentage !== undefined) {
492
+ return `<strong>Progress:</strong> ${message} (${Math.round(percentage)}%)`;
493
+ }
494
+ return `<strong>Progress:</strong> ${message}`;
495
+ } else if (event.subtype === 'analysis:queued') {
496
+ return `<strong>Queued:</strong> Analysis for ${data.path || 'Unknown path'}`;
497
+ } else if (event.subtype === 'analysis:start') {
498
+ return `<strong>Started:</strong> Analyzing ${data.path || 'Unknown path'}`;
499
+ } else if (event.subtype === 'analysis:complete') {
500
+ const duration = data.duration ? ` (${data.duration.toFixed(2)}s)` : '';
501
+ return `<strong>Complete:</strong> Analysis finished${duration}`;
502
+ } else if (event.subtype === 'analysis:error') {
503
+ return `<strong>Error:</strong> ${data.message || 'Analysis failed'}`;
504
+ } else if (event.subtype === 'analysis:cancelled') {
505
+ return `<strong>Cancelled:</strong> Analysis stopped for ${data.path || 'Unknown path'}`;
506
+ } else if (event.subtype === 'file:start') {
507
+ return `<strong>File:</strong> Processing ${data.file || 'Unknown file'}`;
508
+ } else if (event.subtype === 'file:complete') {
509
+ const nodes = data.nodes_count !== undefined ? ` (${data.nodes_count} nodes)` : '';
510
+ return `<strong>File done:</strong> ${data.file || 'Unknown file'}${nodes}`;
511
+ } else if (event.subtype === 'node:found') {
512
+ return `<strong>Node:</strong> Found ${data.node_type || 'element'} "${data.name || 'unnamed'}"`;
513
+ } else if (event.subtype === 'error') {
514
+ return `<strong>Error:</strong> ${data.error || 'Unknown error'} in ${data.file || 'file'}`;
515
+ }
516
+
517
+ // Generic fallback for code events
518
+ const json = JSON.stringify(data);
519
+ return `<strong>Code:</strong> ${json.length > 100 ? json.substring(0, 100) + '...' : json}`;
520
+ }
521
+
483
522
  /**
484
523
  * Format generic event data
485
524
  */
@@ -393,13 +393,25 @@ class Dashboard {
393
393
  console.log('Dashboard triggering activity tree render...');
394
394
  window.activityTreeInstance.renderWhenVisible();
395
395
  }
396
+
397
+ // Force show to ensure the tree is visible
398
+ if (typeof window.activityTreeInstance.forceShow === 'function') {
399
+ console.log('Dashboard forcing activity tree to show...');
400
+ window.activityTreeInstance.forceShow();
401
+ }
396
402
  }
397
403
  } else if (window.activityTree && typeof window.activityTree === 'function') {
398
404
  // Fallback to legacy approach if available
399
405
  const activityTreeInstance = window.activityTree();
400
- if (activityTreeInstance && typeof activityTreeInstance.renderWhenVisible === 'function') {
401
- console.log('Dashboard triggering activity tree render (legacy)...');
402
- activityTreeInstance.renderWhenVisible();
406
+ if (activityTreeInstance) {
407
+ if (typeof activityTreeInstance.renderWhenVisible === 'function') {
408
+ console.log('Dashboard triggering activity tree render (legacy)...');
409
+ activityTreeInstance.renderWhenVisible();
410
+ }
411
+ if (typeof activityTreeInstance.forceShow === 'function') {
412
+ console.log('Dashboard forcing activity tree to show (legacy)...');
413
+ activityTreeInstance.forceShow();
414
+ }
403
415
  }
404
416
  } else {
405
417
  // Module not loaded yet, retry after a delay
@@ -475,11 +475,10 @@ class SocketClient {
475
475
  return;
476
476
  }
477
477
 
478
- // Check if this is a code analysis event - if so, don't add to events list
479
- // Code analysis events are handled by the code-tree component and shown in the footer
478
+ // Code analysis events are now allowed to flow through to the events list for troubleshooting
479
+ // They will appear in both the Events tab and the Code tab
480
480
  if (validatedEvent.type && validatedEvent.type.startsWith('code:')) {
481
- console.log('Code analysis event received via claude_event, not adding to events list:', validatedEvent.type);
482
- return;
481
+ console.log('Code analysis event received via claude_event, adding to events list for troubleshooting:', validatedEvent.type);
483
482
  }
484
483
 
485
484
  // Transform event to match expected format (for backward compatibility)
@@ -551,46 +550,55 @@ class SocketClient {
551
550
  this.addEvent({ type: 'log', subtype: 'entry', timestamp: new Date().toISOString(), data });
552
551
  });
553
552
 
554
- // Code analysis events - don't add to event list, just pass through
555
- // These are handled by the code-tree component and shown in the footer
553
+ // Code analysis events - now allowed to flow through for troubleshooting
554
+ // These are ALSO handled by the code-tree component and shown in the footer
555
+ // They will appear in both places: Events tab (for troubleshooting) and Code tab (for visualization)
556
556
  this.socket.on('code:analysis:queued', (data) => {
557
- // Don't add to events list - handled by code-tree component
558
- console.log('Code analysis queued event received, not adding to events list');
557
+ // Add to events list for troubleshooting
558
+ console.log('Code analysis queued event received, adding to events list for troubleshooting');
559
+ this.addEvent({ type: 'code', subtype: 'analysis:queued', timestamp: new Date().toISOString(), data });
559
560
  });
560
561
 
561
562
  this.socket.on('code:analysis:accepted', (data) => {
562
- // Don't add to events list
563
- console.log('Code analysis accepted event received, not adding to events list');
563
+ // Add to events list for troubleshooting
564
+ console.log('Code analysis accepted event received, adding to events list for troubleshooting');
565
+ this.addEvent({ type: 'code', subtype: 'analysis:accepted', timestamp: new Date().toISOString(), data });
564
566
  });
565
567
 
566
568
  this.socket.on('code:analysis:start', (data) => {
567
- // Don't add to events list
568
- console.log('Code analysis start event received, not adding to events list');
569
+ // Add to events list for troubleshooting
570
+ console.log('Code analysis start event received, adding to events list for troubleshooting');
571
+ this.addEvent({ type: 'code', subtype: 'analysis:start', timestamp: new Date().toISOString(), data });
569
572
  });
570
573
 
571
574
  this.socket.on('code:analysis:complete', (data) => {
572
- // Don't add to events list
573
- console.log('Code analysis complete event received, not adding to events list');
575
+ // Add to events list for troubleshooting
576
+ console.log('Code analysis complete event received, adding to events list for troubleshooting');
577
+ this.addEvent({ type: 'code', subtype: 'analysis:complete', timestamp: new Date().toISOString(), data });
574
578
  });
575
579
 
576
580
  this.socket.on('code:analysis:error', (data) => {
577
- // Don't add to events list
578
- console.log('Code analysis error event received, not adding to events list');
581
+ // Add to events list for troubleshooting
582
+ console.log('Code analysis error event received, adding to events list for troubleshooting');
583
+ this.addEvent({ type: 'code', subtype: 'analysis:error', timestamp: new Date().toISOString(), data });
579
584
  });
580
585
 
581
586
  this.socket.on('code:file:start', (data) => {
582
- // Don't add to events list
583
- console.log('Code file start event received, not adding to events list');
587
+ // Add to events list for troubleshooting
588
+ console.log('Code file start event received, adding to events list for troubleshooting');
589
+ this.addEvent({ type: 'code', subtype: 'file:start', timestamp: new Date().toISOString(), data });
584
590
  });
585
591
 
586
592
  this.socket.on('code:node:found', (data) => {
587
- // Don't add to events list
588
- console.log('Code node found event received, not adding to events list');
593
+ // Add to events list for troubleshooting
594
+ console.log('Code node found event received, adding to events list for troubleshooting');
595
+ this.addEvent({ type: 'code', subtype: 'node:found', timestamp: new Date().toISOString(), data });
589
596
  });
590
597
 
591
598
  this.socket.on('code:analysis:progress', (data) => {
592
- // Don't add to events list
593
- console.log('Code analysis progress event received, not adding to events list');
599
+ // Add to events list for troubleshooting
600
+ console.log('Code analysis progress event received, adding to events list for troubleshooting');
601
+ this.addEvent({ type: 'code', subtype: 'analysis:progress', timestamp: new Date().toISOString(), data });
594
602
  });
595
603
 
596
604
  this.socket.on('history', (data) => {
@@ -1082,6 +1090,7 @@ class SocketClient {
1082
1090
  // 1. Hook events: { type: 'hook.pre_tool', timestamp: '...', data: {...} }
1083
1091
  // 2. Legacy events: { event: 'TestStart', timestamp: '...', ... }
1084
1092
  // 3. Standard events: { type: 'session', subtype: 'started', ... }
1093
+ // 4. Normalized events: { type: 'code', subtype: 'progress', ... } - already normalized, keep as-is
1085
1094
 
1086
1095
  if (!eventData) {
1087
1096
  return eventData; // Return as-is if null/undefined
@@ -1089,8 +1098,26 @@ class SocketClient {
1089
1098
 
1090
1099
  let transformedEvent = { ...eventData };
1091
1100
 
1101
+ // Check if event is already normalized (has both type and subtype as separate fields)
1102
+ // This prevents double-transformation of events that were normalized on the backend
1103
+ const isAlreadyNormalized = eventData.type && eventData.subtype &&
1104
+ !eventData.type.includes('.') &&
1105
+ !eventData.type.includes(':');
1106
+
1107
+ if (isAlreadyNormalized) {
1108
+ // Event is already properly normalized from backend, just preserve it
1109
+ // Store a composite originalEventName for display if needed
1110
+ if (!transformedEvent.originalEventName) {
1111
+ if (eventData.subtype === 'generic' || eventData.type === eventData.subtype) {
1112
+ transformedEvent.originalEventName = eventData.type;
1113
+ } else {
1114
+ transformedEvent.originalEventName = `${eventData.type}.${eventData.subtype}`;
1115
+ }
1116
+ }
1117
+ // Return early to avoid further transformation
1118
+ }
1092
1119
  // Handle legacy format with 'event' field but no 'type'
1093
- if (!eventData.type && eventData.event) {
1120
+ else if (!eventData.type && eventData.event) {
1094
1121
  // Map common event names to proper type/subtype
1095
1122
  const eventName = eventData.event;
1096
1123
 
@@ -1121,8 +1148,10 @@ class SocketClient {
1121
1148
 
1122
1149
  // Remove the 'event' field to avoid confusion
1123
1150
  delete transformedEvent.event;
1151
+ // Store original event name for display purposes
1152
+ transformedEvent.originalEventName = eventName;
1124
1153
  }
1125
- // Handle standard format with 'type' field
1154
+ // Handle standard format with 'type' field that needs transformation
1126
1155
  else if (eventData.type) {
1127
1156
  const type = eventData.type;
1128
1157
 
@@ -1131,30 +1160,43 @@ class SocketClient {
1131
1160
  const subtype = type.substring(5); // Remove 'hook.' prefix
1132
1161
  transformedEvent.type = 'hook';
1133
1162
  transformedEvent.subtype = subtype;
1163
+ transformedEvent.originalEventName = type;
1134
1164
  }
1135
1165
  // Transform 'code:*' events to proper code type
1166
+ // Handle multi-level subtypes like 'code:analysis:queued'
1136
1167
  else if (type.startsWith('code:')) {
1137
1168
  transformedEvent.type = 'code';
1138
- transformedEvent.subtype = type.substring(5); // Remove 'code:' prefix
1169
+ // Replace colons with underscores in subtype for consistency
1170
+ const subtypePart = type.substring(5); // Remove 'code:' prefix
1171
+ transformedEvent.subtype = subtypePart.replace(/:/g, '_');
1172
+ transformedEvent.originalEventName = type;
1139
1173
  }
1140
1174
  // Transform other dotted types like 'session.started' -> type: 'session', subtype: 'started'
1141
1175
  else if (type.includes('.')) {
1142
1176
  const [mainType, ...subtypeParts] = type.split('.');
1143
1177
  transformedEvent.type = mainType;
1144
1178
  transformedEvent.subtype = subtypeParts.join('.');
1179
+ transformedEvent.originalEventName = type;
1180
+ }
1181
+ // Transform any remaining colon-separated types generically
1182
+ else if (type.includes(':')) {
1183
+ const parts = type.split(':', 2); // Split into max 2 parts
1184
+ transformedEvent.type = parts[0];
1185
+ // Replace any remaining colons with underscores in subtype
1186
+ transformedEvent.subtype = parts.length > 1 ? parts[1].replace(/:/g, '_') : 'generic';
1187
+ transformedEvent.originalEventName = type;
1188
+ }
1189
+ // If type doesn't need transformation but has no subtype, set a default
1190
+ else if (!eventData.subtype) {
1191
+ transformedEvent.subtype = 'generic';
1192
+ transformedEvent.originalEventName = type;
1145
1193
  }
1146
1194
  }
1147
1195
  // If no type and no event field, mark as unknown
1148
1196
  else {
1149
1197
  transformedEvent.type = 'unknown';
1150
1198
  transformedEvent.subtype = '';
1151
- }
1152
-
1153
- // Store original event name for display purposes (before any transformation)
1154
- if (!eventData.type && eventData.event) {
1155
- transformedEvent.originalEventName = eventData.event;
1156
- } else if (eventData.type) {
1157
- transformedEvent.originalEventName = eventData.type;
1199
+ transformedEvent.originalEventName = 'unknown';
1158
1200
  }
1159
1201
 
1160
1202
  // Extract and flatten data fields to top level for dashboard compatibility
@@ -389,12 +389,9 @@
389
389
  <!-- Code Tab -->
390
390
  <div class="tab-content" id="code-tab">
391
391
  <div class="code-container">
392
- <!-- Compact header with all controls in one line -->
392
+ <!-- Simplified header with controls -->
393
393
  <div class="code-header-compact">
394
394
  <div class="header-left">
395
- <input type="text" id="analysis-path" placeholder="Path" value="." class="path-input-compact">
396
- <button id="analyze-code" class="btn-compact btn-primary">🔍</button>
397
- <button id="cancel-analysis" class="btn-compact btn-danger" style="display: none;">✕</button>
398
395
  <button id="code-expand-all" class="btn-compact" title="Expand All">⊕</button>
399
396
  <button id="code-collapse-all" class="btn-compact" title="Collapse All">⊖</button>
400
397
  <button id="code-reset-zoom" class="btn-compact" title="Reset Zoom">⟲</button>
@@ -416,22 +413,23 @@
416
413
  <input type="text" id="code-search" placeholder="Search..." class="search-compact">
417
414
  </div>
418
415
  </div>
419
- <!-- Collapsible advanced options -->
420
- <details class="code-advanced-options">
421
- <summary>Advanced Options</summary>
416
+ <!-- Advanced options - visible by default -->
417
+ <div class="code-advanced-options-visible">
422
418
  <div class="advanced-content">
423
419
  <div class="option-group">
424
420
  <label>Languages:</label>
425
421
  <label><input type="checkbox" class="language-checkbox" value="python" checked> Python</label>
426
422
  <label><input type="checkbox" class="language-checkbox" value="javascript" checked> JS</label>
427
- <label><input type="checkbox" class="language-checkbox" value="typescript"> TS</label>
423
+ <label><input type="checkbox" class="language-checkbox" value="typescript" checked> TS</label>
428
424
  </div>
429
425
  <div class="option-group">
430
- <label>Depth: <input type="number" id="max-depth" min="1" max="10" value="5" class="input-compact"></label>
431
- <label>Ignore: <input type="text" id="ignore-patterns" placeholder="test*, *.spec.js" class="input-compact"></label>
426
+ <label>Ignore: <input type="text" id="ignore-patterns" placeholder="test*, *.spec.js, node_modules" class="input-compact" style="width: 200px;"></label>
427
+ </div>
428
+ <div class="option-group">
429
+ <label><input type="checkbox" id="show-hidden-files"> Show hidden files (dotfiles)</label>
432
430
  </div>
433
431
  </div>
434
- </details>
432
+ </div>
435
433
  <div id="code-tree-container" class="code-tree-container">
436
434
  <div id="code-tree"></div>
437
435
  <!-- Collapsible legend -->
@@ -62,7 +62,9 @@ class MemoryFormatService:
62
62
  line = line.strip()
63
63
  # Skip headers, empty lines, and metadata
64
64
  if (
65
- not line or line.startswith(("#", "Last Updated:", "**")) or line == "---"
65
+ not line
66
+ or line.startswith(("#", "Last Updated:", "**"))
67
+ or line == "---"
66
68
  ):
67
69
  continue
68
70
 
@@ -233,10 +233,7 @@ class AgentCleanupService(IAgentCleanupService):
233
233
  all_agents = multi_source_service.discover_agents_from_all_sources()
234
234
 
235
235
  # Detect orphaned agents
236
- return multi_source_service.detect_orphaned_agents(
237
- agents_dir, all_agents
238
- )
239
-
236
+ return multi_source_service.detect_orphaned_agents(agents_dir, all_agents)
240
237
 
241
238
  except Exception as e:
242
239
  self.logger.error(f"Error finding orphaned agents: {e}", exc_info=True)
@@ -177,7 +177,6 @@ class StartupCheckerService(IStartupChecker):
177
177
  try:
178
178
  # Check Python version
179
179
 
180
-
181
180
  # Check for common missing directories
182
181
  warnings.extend(self._check_required_directories())
183
182
 
@@ -307,4 +307,3 @@ class CacheManager(ICacheManager):
307
307
  },
308
308
  "fs_cache": self._fs_cache.get_stats() if self._fs_cache else {},
309
309
  }
310
-
@@ -59,6 +59,7 @@ class EventType(Enum):
59
59
  PERFORMANCE = "performance" # Performance metrics
60
60
  CLAUDE = "claude" # Claude process events
61
61
  TEST = "test" # Test events
62
+ CODE = "code" # Code analysis events
62
63
  TOOL = "tool" # Tool events
63
64
  SUBAGENT = "subagent" # Subagent events
64
65
 
@@ -351,6 +352,41 @@ class EventNormalizer:
351
352
  event_type.value if isinstance(event_type, EventType) else event_type
352
353
  ), subtype
353
354
 
355
+ # Handle colon-separated event names (e.g., "code:analysis:queued", "code:progress")
356
+ # These are commonly used by the code analysis system
357
+ if ":" in event_name:
358
+ parts = event_name.split(":", 2) # Split into max 3 parts
359
+ if len(parts) >= 2:
360
+ type_part = parts[0].lower()
361
+ # For events like "code:analysis:queued", combine the last parts as subtype
362
+ # Replace colons with underscores for clean subtypes
363
+ if len(parts) == 3:
364
+ subtype_part = f"{parts[1]}_{parts[2]}"
365
+ else:
366
+ subtype_part = parts[1].replace(":", "_")
367
+
368
+ # Map the type part to known types
369
+ if type_part in [
370
+ "code", # Code analysis events
371
+ "hook",
372
+ "session",
373
+ "file",
374
+ "system",
375
+ "connection",
376
+ "memory",
377
+ "git",
378
+ "todo",
379
+ "ticket",
380
+ "agent",
381
+ "claude",
382
+ "error",
383
+ "performance",
384
+ "test",
385
+ "tool",
386
+ "subagent",
387
+ ]:
388
+ return type_part, subtype_part
389
+
354
390
  # Handle dotted event names (e.g., "connection.status", "session.started")
355
391
  if "." in event_name:
356
392
  parts = event_name.split(".", 1)
@@ -443,6 +479,34 @@ class EventNormalizer:
443
479
  return EventType.MEMORY.value, "injected"
444
480
  return EventType.MEMORY.value, "generic"
445
481
 
482
+ # Code analysis events - using underscores for clean subtypes
483
+ if "code" in event_lower:
484
+ if "analysis" in event_lower:
485
+ if "queue" in event_lower:
486
+ return EventType.CODE.value, "analysis_queued"
487
+ if "start" in event_lower:
488
+ return EventType.CODE.value, "analysis_start"
489
+ if "complete" in event_lower:
490
+ return EventType.CODE.value, "analysis_complete"
491
+ if "error" in event_lower:
492
+ return EventType.CODE.value, "analysis_error"
493
+ if "cancel" in event_lower:
494
+ return EventType.CODE.value, "analysis_cancelled"
495
+ return EventType.CODE.value, "analysis_generic"
496
+ if "progress" in event_lower:
497
+ return EventType.CODE.value, "progress"
498
+ if "file" in event_lower:
499
+ if "discovered" in event_lower:
500
+ return EventType.CODE.value, "file_discovered"
501
+ if "analyzed" in event_lower:
502
+ return EventType.CODE.value, "file_analyzed"
503
+ return EventType.CODE.value, "file_complete"
504
+ if "directory" in event_lower:
505
+ return EventType.CODE.value, "directory_discovered"
506
+ if "node" in event_lower:
507
+ return EventType.CODE.value, "node_found"
508
+ return EventType.CODE.value, "generic"
509
+
446
510
  # Default to unknown with lowercase subtype
447
511
  return "unknown", event_name.lower() if event_name else ""
448
512