claude-mpm 4.1.8__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 (111) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +26 -1
  3. claude_mpm/agents/agents_metadata.py +57 -0
  4. claude_mpm/agents/templates/.claude-mpm/memories/README.md +17 -0
  5. claude_mpm/agents/templates/.claude-mpm/memories/engineer_memories.md +3 -0
  6. claude_mpm/agents/templates/agent-manager.json +263 -17
  7. claude_mpm/agents/templates/agentic_coder_optimizer.json +222 -0
  8. claude_mpm/agents/templates/code_analyzer.json +18 -8
  9. claude_mpm/agents/templates/engineer.json +1 -1
  10. claude_mpm/agents/templates/logs/prompts/agent_engineer_20250826_014258_728.md +39 -0
  11. claude_mpm/agents/templates/qa.json +1 -1
  12. claude_mpm/agents/templates/research.json +1 -1
  13. claude_mpm/cli/__init__.py +15 -0
  14. claude_mpm/cli/commands/__init__.py +6 -0
  15. claude_mpm/cli/commands/analyze.py +548 -0
  16. claude_mpm/cli/commands/analyze_code.py +524 -0
  17. claude_mpm/cli/commands/configure.py +78 -28
  18. claude_mpm/cli/commands/configure_tui.py +62 -60
  19. claude_mpm/cli/commands/dashboard.py +288 -0
  20. claude_mpm/cli/commands/debug.py +1386 -0
  21. claude_mpm/cli/commands/mpm_init.py +427 -0
  22. claude_mpm/cli/commands/mpm_init_handler.py +83 -0
  23. claude_mpm/cli/parsers/analyze_code_parser.py +170 -0
  24. claude_mpm/cli/parsers/analyze_parser.py +135 -0
  25. claude_mpm/cli/parsers/base_parser.py +44 -0
  26. claude_mpm/cli/parsers/dashboard_parser.py +113 -0
  27. claude_mpm/cli/parsers/debug_parser.py +319 -0
  28. claude_mpm/cli/parsers/mpm_init_parser.py +122 -0
  29. claude_mpm/constants.py +13 -1
  30. claude_mpm/core/framework_loader.py +148 -6
  31. claude_mpm/core/log_manager.py +16 -13
  32. claude_mpm/core/logger.py +1 -1
  33. claude_mpm/core/unified_agent_registry.py +1 -1
  34. claude_mpm/dashboard/.claude-mpm/socketio-instances.json +1 -0
  35. claude_mpm/dashboard/analysis_runner.py +455 -0
  36. claude_mpm/dashboard/static/built/components/activity-tree.js +2 -0
  37. claude_mpm/dashboard/static/built/components/agent-inference.js +1 -1
  38. claude_mpm/dashboard/static/built/components/code-tree.js +2 -0
  39. claude_mpm/dashboard/static/built/components/code-viewer.js +2 -0
  40. claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
  41. claude_mpm/dashboard/static/built/components/file-tool-tracker.js +1 -1
  42. claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
  43. claude_mpm/dashboard/static/built/components/session-manager.js +1 -1
  44. claude_mpm/dashboard/static/built/components/working-directory.js +1 -1
  45. claude_mpm/dashboard/static/built/dashboard.js +1 -1
  46. claude_mpm/dashboard/static/built/socket-client.js +1 -1
  47. claude_mpm/dashboard/static/css/activity.css +549 -0
  48. claude_mpm/dashboard/static/css/code-tree.css +1175 -0
  49. claude_mpm/dashboard/static/css/dashboard.css +245 -0
  50. claude_mpm/dashboard/static/dist/components/activity-tree.js +2 -0
  51. claude_mpm/dashboard/static/dist/components/code-tree.js +2 -0
  52. claude_mpm/dashboard/static/dist/components/code-viewer.js +2 -0
  53. claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
  54. claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
  55. claude_mpm/dashboard/static/dist/components/working-directory.js +1 -1
  56. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  57. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  58. claude_mpm/dashboard/static/js/components/activity-tree.js +1338 -0
  59. claude_mpm/dashboard/static/js/components/code-tree.js +2535 -0
  60. claude_mpm/dashboard/static/js/components/code-viewer.js +480 -0
  61. claude_mpm/dashboard/static/js/components/event-viewer.js +59 -9
  62. claude_mpm/dashboard/static/js/components/session-manager.js +40 -4
  63. claude_mpm/dashboard/static/js/components/socket-manager.js +12 -0
  64. claude_mpm/dashboard/static/js/components/ui-state-manager.js +4 -0
  65. claude_mpm/dashboard/static/js/components/working-directory.js +17 -1
  66. claude_mpm/dashboard/static/js/dashboard.js +51 -0
  67. claude_mpm/dashboard/static/js/socket-client.js +465 -29
  68. claude_mpm/dashboard/templates/index.html +182 -4
  69. claude_mpm/hooks/claude_hooks/hook_handler.py +182 -5
  70. claude_mpm/hooks/claude_hooks/installer.py +386 -113
  71. claude_mpm/scripts/claude-hook-handler.sh +161 -0
  72. claude_mpm/scripts/socketio_daemon.py +121 -8
  73. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +2 -2
  74. claude_mpm/services/agents/deployment/agent_record_service.py +1 -2
  75. claude_mpm/services/agents/memory/memory_format_service.py +1 -3
  76. claude_mpm/services/cli/agent_cleanup_service.py +1 -5
  77. claude_mpm/services/cli/agent_dependency_service.py +1 -1
  78. claude_mpm/services/cli/agent_validation_service.py +3 -4
  79. claude_mpm/services/cli/dashboard_launcher.py +2 -3
  80. claude_mpm/services/cli/startup_checker.py +0 -11
  81. claude_mpm/services/core/cache_manager.py +1 -3
  82. claude_mpm/services/core/path_resolver.py +1 -4
  83. claude_mpm/services/core/service_container.py +2 -2
  84. claude_mpm/services/diagnostics/checks/instructions_check.py +1 -2
  85. claude_mpm/services/infrastructure/monitoring/__init__.py +11 -11
  86. claude_mpm/services/infrastructure/monitoring.py +11 -11
  87. claude_mpm/services/project/architecture_analyzer.py +1 -1
  88. claude_mpm/services/project/dependency_analyzer.py +4 -4
  89. claude_mpm/services/project/language_analyzer.py +3 -3
  90. claude_mpm/services/project/metrics_collector.py +3 -6
  91. claude_mpm/services/socketio/event_normalizer.py +64 -0
  92. claude_mpm/services/socketio/handlers/__init__.py +2 -0
  93. claude_mpm/services/socketio/handlers/code_analysis.py +672 -0
  94. claude_mpm/services/socketio/handlers/registry.py +2 -0
  95. claude_mpm/services/socketio/server/connection_manager.py +6 -4
  96. claude_mpm/services/socketio/server/core.py +100 -11
  97. claude_mpm/services/socketio/server/main.py +8 -2
  98. claude_mpm/services/visualization/__init__.py +19 -0
  99. claude_mpm/services/visualization/mermaid_generator.py +938 -0
  100. claude_mpm/tools/__main__.py +208 -0
  101. claude_mpm/tools/code_tree_analyzer.py +1596 -0
  102. claude_mpm/tools/code_tree_builder.py +631 -0
  103. claude_mpm/tools/code_tree_events.py +416 -0
  104. claude_mpm/tools/socketio_debug.py +671 -0
  105. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/METADATA +2 -1
  106. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/RECORD +110 -74
  107. claude_mpm/agents/schema/agent_schema.json +0 -314
  108. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/WHEEL +0 -0
  109. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/entry_points.txt +0 -0
  110. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/licenses/LICENSE +0 -0
  111. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,416 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Code Tree Event Emitter
4
+ =======================
5
+
6
+ WHY: Provides incremental event emission for real-time code tree visualization
7
+ in the dashboard. Uses Socket.IO to stream events as code is analyzed.
8
+
9
+ DESIGN DECISIONS:
10
+ - Support lazy loading with directory discovery
11
+ - Emit events only for structural elements (directories, files, main functions)
12
+ - Filter out internal functions and handlers
13
+ - Batch events for performance (emit every 10 nodes or 100ms)
14
+ - Use clean event subtypes without colons
15
+ """
16
+
17
+ import json
18
+ import threading
19
+ import time
20
+ from collections import deque
21
+ from dataclasses import asdict, dataclass
22
+ from datetime import datetime
23
+ from typing import Any, Dict, List, Optional
24
+
25
+ try:
26
+ import socketio
27
+
28
+ SOCKETIO_AVAILABLE = True
29
+ except ImportError:
30
+ SOCKETIO_AVAILABLE = False
31
+ socketio = None
32
+
33
+ from pathlib import Path
34
+
35
+ from ..core.logging_config import get_logger
36
+
37
+
38
+ @dataclass
39
+ class CodeNodeEvent:
40
+ """Represents a code node discovery event."""
41
+
42
+ file_path: str
43
+ node_type: str # 'class', 'function', 'method', 'module', 'import'
44
+ name: str
45
+ line_start: int
46
+ line_end: int
47
+ complexity: int = 0
48
+ has_docstring: bool = False
49
+ decorators: List[str] = None
50
+ parent: Optional[str] = None
51
+ children_count: int = 0
52
+ language: str = "python"
53
+
54
+ def to_dict(self) -> Dict[str, Any]:
55
+ """Convert to dictionary for JSON serialization."""
56
+ data = asdict(self)
57
+ data["timestamp"] = datetime.utcnow().isoformat()
58
+ return data
59
+
60
+
61
+ class CodeTreeEventEmitter:
62
+ """Emits code analysis events to dashboard via Socket.IO.
63
+
64
+ WHY: Real-time streaming of analysis progress allows users to see
65
+ the tree building incrementally, providing feedback for large codebases.
66
+ """
67
+
68
+ # Event types - using underscores for clean subtypes
69
+ EVENT_DIRECTORY_DISCOVERED = "code:directory:discovered"
70
+ EVENT_FILE_DISCOVERED = "code:file:discovered"
71
+ EVENT_FILE_ANALYZED = "code:file:analyzed"
72
+ EVENT_NODE_FOUND = "code:node:found"
73
+ EVENT_ANALYSIS_START = "code:analysis:start"
74
+ EVENT_ANALYSIS_COMPLETE = "code:analysis:complete"
75
+ EVENT_PROGRESS = "code:analysis:progress"
76
+ EVENT_ERROR = "code:analysis:error"
77
+
78
+ def __init__(
79
+ self,
80
+ socketio_url: str = "http://localhost:8765",
81
+ batch_size: int = 10,
82
+ batch_timeout: float = 0.1,
83
+ use_stdout: bool = False,
84
+ ):
85
+ """Initialize event emitter.
86
+
87
+ Args:
88
+ socketio_url: Socket.IO server URL
89
+ batch_size: Number of events to batch before emitting
90
+ batch_timeout: Maximum time to wait before emitting batch (seconds)
91
+ use_stdout: If True, emit to stdout instead of Socket.IO
92
+ """
93
+ self.logger = get_logger(__name__)
94
+ self.socketio_url = socketio_url
95
+ self.batch_size = batch_size
96
+ self.batch_timeout = batch_timeout
97
+ self.use_stdout = use_stdout
98
+
99
+ # Event buffer for batching
100
+ self.event_buffer = deque()
101
+ self.buffer_lock = threading.Lock()
102
+ self.last_emit_time = time.time()
103
+
104
+ # Socket.IO client
105
+ self.sio = None
106
+ self.connected = False
107
+ if not use_stdout:
108
+ self._init_socketio()
109
+
110
+ # Statistics
111
+ self.stats = {
112
+ "events_sent": 0,
113
+ "events_buffered": 0,
114
+ "files_processed": 0,
115
+ "nodes_found": 0,
116
+ "errors": 0,
117
+ "start_time": None,
118
+ }
119
+
120
+ # Background task for periodic emission
121
+ self._emit_task = None
122
+ self._stop_event = threading.Event()
123
+
124
+ def _init_socketio(self):
125
+ """Initialize Socket.IO client connection."""
126
+ if not SOCKETIO_AVAILABLE:
127
+ self.logger.warning("Socket.IO not available - events will be logged only")
128
+ return
129
+
130
+ try:
131
+ self.sio = socketio.Client(
132
+ reconnection=True,
133
+ reconnection_attempts=3,
134
+ reconnection_delay=1,
135
+ logger=False,
136
+ engineio_logger=False,
137
+ )
138
+
139
+ @self.sio.event
140
+ def connect():
141
+ self.connected = True
142
+ self.logger.info(
143
+ f"Connected to Socket.IO server at {self.socketio_url}"
144
+ )
145
+
146
+ @self.sio.event
147
+ def disconnect():
148
+ self.connected = False
149
+ self.logger.info("Disconnected from Socket.IO server")
150
+
151
+ # Attempt connection
152
+ self.sio.connect(self.socketio_url, wait_timeout=2)
153
+
154
+ except Exception as e:
155
+ self.logger.warning(f"Failed to connect to Socket.IO: {e}")
156
+ self.sio = None
157
+
158
+ def start(self):
159
+ """Start the event emitter and background tasks."""
160
+ self.stats["start_time"] = datetime.utcnow()
161
+ self._stop_event.clear()
162
+
163
+ # Start background emit task
164
+ self._emit_task = threading.Thread(target=self._emit_loop, daemon=True)
165
+ self._emit_task.start()
166
+
167
+ # Emit analysis start event
168
+ self.emit(
169
+ self.EVENT_ANALYSIS_START,
170
+ {
171
+ "timestamp": datetime.utcnow().isoformat(),
172
+ "batch_size": self.batch_size,
173
+ "batch_timeout": self.batch_timeout,
174
+ },
175
+ )
176
+
177
+ def stop(self):
178
+ """Stop the event emitter and flush remaining events."""
179
+ # Flush remaining events
180
+ self._flush_events()
181
+
182
+ # Emit analysis complete event with statistics
183
+ self.emit(
184
+ self.EVENT_ANALYSIS_COMPLETE,
185
+ {
186
+ "timestamp": datetime.utcnow().isoformat(),
187
+ "duration": (
188
+ (datetime.utcnow() - self.stats["start_time"]).total_seconds()
189
+ if self.stats["start_time"]
190
+ else 0
191
+ ),
192
+ "stats": self.stats,
193
+ },
194
+ )
195
+
196
+ # Stop background task
197
+ self._stop_event.set()
198
+ if self._emit_task:
199
+ self._emit_task.join(timeout=1)
200
+
201
+ # Disconnect Socket.IO
202
+ if self.sio and self.connected:
203
+ self.sio.disconnect()
204
+
205
+ def emit(self, event_type: str, data: Dict[str, Any], batch: bool = False):
206
+ """Emit an event, either immediately or batched.
207
+
208
+ Args:
209
+ event_type: Type of event to emit
210
+ data: Event data
211
+ batch: Whether to batch this event
212
+ """
213
+ event = {
214
+ "type": event_type,
215
+ "data": data,
216
+ "timestamp": datetime.utcnow().isoformat(),
217
+ }
218
+
219
+ if batch:
220
+ with self.buffer_lock:
221
+ self.event_buffer.append(event)
222
+ self.stats["events_buffered"] += 1
223
+
224
+ # Check if we should flush
225
+ if len(self.event_buffer) >= self.batch_size:
226
+ self._flush_events()
227
+ else:
228
+ self._emit_event(event)
229
+
230
+ def emit_directory_discovered(self, dir_path: str, children: List[Dict[str, Any]]):
231
+ """Emit directory discovery event."""
232
+ self.emit(
233
+ self.EVENT_DIRECTORY_DISCOVERED,
234
+ {
235
+ "path": dir_path,
236
+ "name": Path(dir_path).name,
237
+ "children": children,
238
+ "type": "directory",
239
+ },
240
+ )
241
+
242
+ def emit_file_discovered(
243
+ self, file_path: str, language: Optional[str] = None, size: int = 0
244
+ ):
245
+ """Emit file discovery event."""
246
+ self.emit(
247
+ self.EVENT_FILE_DISCOVERED,
248
+ {
249
+ "path": file_path,
250
+ "name": Path(file_path).name,
251
+ "language": language or "unknown",
252
+ "size": size,
253
+ "type": "file",
254
+ },
255
+ )
256
+
257
+ def emit_file_start(self, file_path: str, language: Optional[str] = None):
258
+ """Emit file analysis start event.
259
+
260
+ WHY: Signals the beginning of file analysis for progress tracking.
261
+ """
262
+ self.emit(
263
+ "code:file_start",
264
+ {
265
+ "path": file_path,
266
+ "name": Path(file_path).name,
267
+ "language": language or "unknown",
268
+ "type": "file",
269
+ "status": "analyzing",
270
+ },
271
+ )
272
+
273
+ def emit_file_complete(
274
+ self, file_path: str, nodes_count: int = 0, duration: float = 0
275
+ ):
276
+ """Emit file analysis complete event.
277
+
278
+ WHY: Signals completion of file analysis with summary statistics.
279
+ """
280
+ self.emit(
281
+ "code:file_complete",
282
+ {
283
+ "path": file_path,
284
+ "name": Path(file_path).name,
285
+ "nodes_count": nodes_count,
286
+ "duration": duration,
287
+ "type": "file",
288
+ "status": "complete",
289
+ },
290
+ )
291
+
292
+ def emit_file_analyzed(
293
+ self, file_path: str, nodes: List[Dict[str, Any]], duration: float = 0
294
+ ):
295
+ """Emit file analysis complete event."""
296
+ self.stats["files_processed"] += 1
297
+ self.emit(
298
+ self.EVENT_FILE_ANALYZED,
299
+ {
300
+ "path": file_path,
301
+ "nodes": nodes,
302
+ "nodes_count": len(nodes),
303
+ "duration": duration,
304
+ },
305
+ )
306
+
307
+ def emit_node(self, node: CodeNodeEvent):
308
+ """Emit code node discovery event (batched).
309
+
310
+ Filters out internal functions and handlers.
311
+ """
312
+ # Filter out internal handler functions
313
+ if self._is_internal_function(node.name):
314
+ return
315
+
316
+ self.stats["nodes_found"] += 1
317
+ # In stdout mode, don't batch - emit immediately for real-time updates
318
+ batch_mode = not self.use_stdout
319
+ self.emit(self.EVENT_NODE_FOUND, node.to_dict(), batch=batch_mode)
320
+
321
+ def _is_internal_function(self, name: str) -> bool:
322
+ """Check if function is an internal handler that should be filtered."""
323
+ internal_patterns = [
324
+ "handle", # Event handlers
325
+ "on_", # Event callbacks
326
+ "_", # Private methods
327
+ "get_", # Simple getters
328
+ "set_", # Simple setters
329
+ "__", # Python magic methods
330
+ ]
331
+
332
+ name_lower = name.lower()
333
+ return any(name_lower.startswith(pattern) for pattern in internal_patterns)
334
+
335
+ def emit_progress(self, current: int, total: int, message: str = ""):
336
+ """Emit progress update event."""
337
+ self.emit(
338
+ self.EVENT_PROGRESS,
339
+ {
340
+ "current": current,
341
+ "total": total,
342
+ "percentage": (current / total * 100) if total > 0 else 0,
343
+ "message": message,
344
+ },
345
+ )
346
+
347
+ def emit_error(self, file_path: str, error: str):
348
+ """Emit error event."""
349
+ self.stats["errors"] += 1
350
+ self.emit(self.EVENT_ERROR, {"file": file_path, "error": str(error)})
351
+
352
+ def _emit_event(self, event: Dict[str, Any]):
353
+ """Emit a single event."""
354
+
355
+ # Convert datetime objects to ISO strings for JSON serialization
356
+ def convert_datetime(obj):
357
+ if isinstance(obj, datetime):
358
+ return obj.isoformat()
359
+ if isinstance(obj, dict):
360
+ return {k: convert_datetime(v) for k, v in obj.items()}
361
+ if isinstance(obj, list):
362
+ return [convert_datetime(item) for item in obj]
363
+ return obj
364
+
365
+ event = convert_datetime(event)
366
+
367
+ if self.use_stdout:
368
+ # Emit to stdout as JSON for subprocess communication
369
+ print(json.dumps(event), flush=True)
370
+ self.stats["events_sent"] += 1
371
+ elif self.sio and self.connected:
372
+ try:
373
+ self.sio.emit("code_tree_event", event)
374
+ self.stats["events_sent"] += 1
375
+ except Exception as e:
376
+ self.logger.error(f"Failed to emit event: {e}")
377
+ else:
378
+ # Fallback to logging
379
+ self.logger.debug(
380
+ f"Event: {event['type']} - {json.dumps(event['data'])[:100]}"
381
+ )
382
+
383
+ def _flush_events(self):
384
+ """Flush all buffered events."""
385
+ with self.buffer_lock:
386
+ if not self.event_buffer:
387
+ return
388
+
389
+ # Emit as batch
390
+ batch_event = {
391
+ "type": "code:batch",
392
+ "data": {
393
+ "events": list(self.event_buffer),
394
+ "count": len(self.event_buffer),
395
+ },
396
+ "timestamp": datetime.utcnow().isoformat(),
397
+ }
398
+
399
+ self._emit_event(batch_event)
400
+ self.event_buffer.clear()
401
+ self.last_emit_time = time.time()
402
+
403
+ def _emit_loop(self):
404
+ """Background loop for periodic event emission."""
405
+ while not self._stop_event.is_set():
406
+ time.sleep(self.batch_timeout)
407
+
408
+ # Check if enough time has passed since last emit
409
+ if time.time() - self.last_emit_time >= self.batch_timeout:
410
+ with self.buffer_lock:
411
+ if self.event_buffer:
412
+ self._flush_events()
413
+
414
+ def get_stats(self) -> Dict[str, Any]:
415
+ """Get current statistics."""
416
+ return self.stats.copy()