claude-mpm 4.1.8__py3-none-any.whl → 4.1.10__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/INSTRUCTIONS.md +26 -1
- claude_mpm/agents/agents_metadata.py +57 -0
- claude_mpm/agents/templates/.claude-mpm/memories/README.md +17 -0
- claude_mpm/agents/templates/.claude-mpm/memories/engineer_memories.md +3 -0
- claude_mpm/agents/templates/agent-manager.json +263 -17
- claude_mpm/agents/templates/agentic_coder_optimizer.json +222 -0
- claude_mpm/agents/templates/code_analyzer.json +18 -8
- claude_mpm/agents/templates/engineer.json +1 -1
- claude_mpm/agents/templates/logs/prompts/agent_engineer_20250826_014258_728.md +39 -0
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/research.json +1 -1
- claude_mpm/cli/__init__.py +4 -0
- claude_mpm/cli/commands/__init__.py +6 -0
- claude_mpm/cli/commands/analyze.py +547 -0
- claude_mpm/cli/commands/analyze_code.py +524 -0
- claude_mpm/cli/commands/configure.py +77 -28
- claude_mpm/cli/commands/configure_tui.py +60 -60
- claude_mpm/cli/commands/debug.py +1387 -0
- claude_mpm/cli/parsers/analyze_code_parser.py +170 -0
- claude_mpm/cli/parsers/analyze_parser.py +135 -0
- claude_mpm/cli/parsers/base_parser.py +29 -0
- claude_mpm/cli/parsers/debug_parser.py +319 -0
- claude_mpm/constants.py +3 -1
- claude_mpm/core/framework_loader.py +148 -6
- claude_mpm/core/log_manager.py +16 -13
- claude_mpm/core/logger.py +1 -1
- claude_mpm/core/unified_agent_registry.py +1 -1
- claude_mpm/dashboard/.claude-mpm/socketio-instances.json +1 -0
- claude_mpm/dashboard/analysis_runner.py +428 -0
- claude_mpm/dashboard/static/built/components/activity-tree.js +2 -0
- claude_mpm/dashboard/static/built/components/agent-inference.js +1 -1
- claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/built/components/file-tool-tracker.js +1 -1
- claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/built/components/session-manager.js +1 -1
- claude_mpm/dashboard/static/built/components/working-directory.js +1 -1
- claude_mpm/dashboard/static/built/dashboard.js +1 -1
- claude_mpm/dashboard/static/built/socket-client.js +1 -1
- claude_mpm/dashboard/static/css/activity.css +549 -0
- claude_mpm/dashboard/static/css/code-tree.css +846 -0
- claude_mpm/dashboard/static/css/dashboard.css +245 -0
- claude_mpm/dashboard/static/dist/components/activity-tree.js +2 -0
- claude_mpm/dashboard/static/dist/components/code-tree.js +2 -0
- claude_mpm/dashboard/static/dist/components/code-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
- claude_mpm/dashboard/static/dist/components/working-directory.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/activity-tree.js +1139 -0
- claude_mpm/dashboard/static/js/components/code-tree.js +1357 -0
- claude_mpm/dashboard/static/js/components/code-viewer.js +480 -0
- claude_mpm/dashboard/static/js/components/event-viewer.js +11 -0
- claude_mpm/dashboard/static/js/components/session-manager.js +40 -4
- claude_mpm/dashboard/static/js/components/socket-manager.js +12 -0
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +4 -0
- claude_mpm/dashboard/static/js/components/working-directory.js +17 -1
- claude_mpm/dashboard/static/js/dashboard.js +39 -0
- claude_mpm/dashboard/static/js/socket-client.js +414 -20
- claude_mpm/dashboard/templates/index.html +184 -4
- claude_mpm/hooks/claude_hooks/hook_handler.py +182 -5
- claude_mpm/hooks/claude_hooks/installer.py +386 -113
- claude_mpm/scripts/claude-hook-handler.sh +161 -0
- claude_mpm/scripts/socketio_daemon.py +121 -8
- claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +2 -2
- claude_mpm/services/agents/deployment/agent_record_service.py +1 -2
- claude_mpm/services/agents/memory/memory_format_service.py +1 -5
- claude_mpm/services/cli/agent_cleanup_service.py +1 -2
- claude_mpm/services/cli/agent_dependency_service.py +1 -1
- claude_mpm/services/cli/agent_validation_service.py +3 -4
- claude_mpm/services/cli/dashboard_launcher.py +2 -3
- claude_mpm/services/cli/startup_checker.py +0 -10
- claude_mpm/services/core/cache_manager.py +1 -2
- claude_mpm/services/core/path_resolver.py +1 -4
- claude_mpm/services/core/service_container.py +2 -2
- claude_mpm/services/diagnostics/checks/instructions_check.py +1 -2
- claude_mpm/services/infrastructure/monitoring/__init__.py +11 -11
- claude_mpm/services/infrastructure/monitoring.py +11 -11
- claude_mpm/services/project/architecture_analyzer.py +1 -1
- claude_mpm/services/project/dependency_analyzer.py +4 -4
- claude_mpm/services/project/language_analyzer.py +3 -3
- claude_mpm/services/project/metrics_collector.py +3 -6
- claude_mpm/services/socketio/handlers/__init__.py +2 -0
- claude_mpm/services/socketio/handlers/code_analysis.py +170 -0
- claude_mpm/services/socketio/handlers/registry.py +2 -0
- claude_mpm/services/socketio/server/connection_manager.py +4 -4
- claude_mpm/services/socketio/server/core.py +100 -11
- claude_mpm/services/socketio/server/main.py +8 -2
- claude_mpm/services/visualization/__init__.py +19 -0
- claude_mpm/services/visualization/mermaid_generator.py +938 -0
- claude_mpm/tools/__main__.py +208 -0
- claude_mpm/tools/code_tree_analyzer.py +778 -0
- claude_mpm/tools/code_tree_builder.py +632 -0
- claude_mpm/tools/code_tree_events.py +318 -0
- claude_mpm/tools/socketio_debug.py +671 -0
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.10.dist-info}/METADATA +1 -1
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.10.dist-info}/RECORD +102 -73
- claude_mpm/agents/schema/agent_schema.json +0 -314
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.10.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.10.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.10.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.10.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,318 @@
|
|
|
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
|
+
- Batch events for performance (emit every 10 nodes or 100ms)
|
|
11
|
+
- Include progress tracking for large codebases
|
|
12
|
+
- Use structured event format for dashboard consumption
|
|
13
|
+
- Support resumable processing with event checkpoints
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import threading
|
|
18
|
+
import time
|
|
19
|
+
from collections import deque
|
|
20
|
+
from dataclasses import asdict, dataclass
|
|
21
|
+
from datetime import datetime
|
|
22
|
+
from typing import Any, Dict, List, Optional
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
import socketio
|
|
26
|
+
|
|
27
|
+
SOCKETIO_AVAILABLE = True
|
|
28
|
+
except ImportError:
|
|
29
|
+
SOCKETIO_AVAILABLE = False
|
|
30
|
+
socketio = None
|
|
31
|
+
|
|
32
|
+
from ..core.logging_config import get_logger
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class CodeNodeEvent:
|
|
37
|
+
"""Represents a code node discovery event."""
|
|
38
|
+
|
|
39
|
+
file_path: str
|
|
40
|
+
node_type: str # 'class', 'function', 'method', 'module', 'import'
|
|
41
|
+
name: str
|
|
42
|
+
line_start: int
|
|
43
|
+
line_end: int
|
|
44
|
+
complexity: int = 0
|
|
45
|
+
has_docstring: bool = False
|
|
46
|
+
decorators: List[str] = None
|
|
47
|
+
parent: Optional[str] = None
|
|
48
|
+
children_count: int = 0
|
|
49
|
+
language: str = "python"
|
|
50
|
+
|
|
51
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
52
|
+
"""Convert to dictionary for JSON serialization."""
|
|
53
|
+
data = asdict(self)
|
|
54
|
+
data["timestamp"] = datetime.utcnow().isoformat()
|
|
55
|
+
return data
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class CodeTreeEventEmitter:
|
|
59
|
+
"""Emits code analysis events to dashboard via Socket.IO.
|
|
60
|
+
|
|
61
|
+
WHY: Real-time streaming of analysis progress allows users to see
|
|
62
|
+
the tree building incrementally, providing feedback for large codebases.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
# Event types
|
|
66
|
+
EVENT_FILE_START = "code:file:start"
|
|
67
|
+
EVENT_FILE_COMPLETE = "code:file:complete"
|
|
68
|
+
EVENT_NODE_FOUND = "code:node:found"
|
|
69
|
+
EVENT_ANALYSIS_START = "code:analysis:start"
|
|
70
|
+
EVENT_ANALYSIS_COMPLETE = "code:analysis:complete"
|
|
71
|
+
EVENT_PROGRESS = "code:progress"
|
|
72
|
+
EVENT_ERROR = "code:error"
|
|
73
|
+
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
socketio_url: str = "http://localhost:8765",
|
|
77
|
+
batch_size: int = 10,
|
|
78
|
+
batch_timeout: float = 0.1,
|
|
79
|
+
use_stdout: bool = False,
|
|
80
|
+
):
|
|
81
|
+
"""Initialize event emitter.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
socketio_url: Socket.IO server URL
|
|
85
|
+
batch_size: Number of events to batch before emitting
|
|
86
|
+
batch_timeout: Maximum time to wait before emitting batch (seconds)
|
|
87
|
+
use_stdout: If True, emit to stdout instead of Socket.IO
|
|
88
|
+
"""
|
|
89
|
+
self.logger = get_logger(__name__)
|
|
90
|
+
self.socketio_url = socketio_url
|
|
91
|
+
self.batch_size = batch_size
|
|
92
|
+
self.batch_timeout = batch_timeout
|
|
93
|
+
self.use_stdout = use_stdout
|
|
94
|
+
|
|
95
|
+
# Event buffer for batching
|
|
96
|
+
self.event_buffer = deque()
|
|
97
|
+
self.buffer_lock = threading.Lock()
|
|
98
|
+
self.last_emit_time = time.time()
|
|
99
|
+
|
|
100
|
+
# Socket.IO client
|
|
101
|
+
self.sio = None
|
|
102
|
+
self.connected = False
|
|
103
|
+
if not use_stdout:
|
|
104
|
+
self._init_socketio()
|
|
105
|
+
|
|
106
|
+
# Statistics
|
|
107
|
+
self.stats = {
|
|
108
|
+
"events_sent": 0,
|
|
109
|
+
"events_buffered": 0,
|
|
110
|
+
"files_processed": 0,
|
|
111
|
+
"nodes_found": 0,
|
|
112
|
+
"errors": 0,
|
|
113
|
+
"start_time": None,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# Background task for periodic emission
|
|
117
|
+
self._emit_task = None
|
|
118
|
+
self._stop_event = threading.Event()
|
|
119
|
+
|
|
120
|
+
def _init_socketio(self):
|
|
121
|
+
"""Initialize Socket.IO client connection."""
|
|
122
|
+
if not SOCKETIO_AVAILABLE:
|
|
123
|
+
self.logger.warning("Socket.IO not available - events will be logged only")
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
self.sio = socketio.Client(
|
|
128
|
+
reconnection=True,
|
|
129
|
+
reconnection_attempts=3,
|
|
130
|
+
reconnection_delay=1,
|
|
131
|
+
logger=False,
|
|
132
|
+
engineio_logger=False,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
@self.sio.event
|
|
136
|
+
def connect():
|
|
137
|
+
self.connected = True
|
|
138
|
+
self.logger.info(
|
|
139
|
+
f"Connected to Socket.IO server at {self.socketio_url}"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
@self.sio.event
|
|
143
|
+
def disconnect():
|
|
144
|
+
self.connected = False
|
|
145
|
+
self.logger.info("Disconnected from Socket.IO server")
|
|
146
|
+
|
|
147
|
+
# Attempt connection
|
|
148
|
+
self.sio.connect(self.socketio_url, wait_timeout=2)
|
|
149
|
+
|
|
150
|
+
except Exception as e:
|
|
151
|
+
self.logger.warning(f"Failed to connect to Socket.IO: {e}")
|
|
152
|
+
self.sio = None
|
|
153
|
+
|
|
154
|
+
def start(self):
|
|
155
|
+
"""Start the event emitter and background tasks."""
|
|
156
|
+
self.stats["start_time"] = datetime.utcnow()
|
|
157
|
+
self._stop_event.clear()
|
|
158
|
+
|
|
159
|
+
# Start background emit task
|
|
160
|
+
self._emit_task = threading.Thread(target=self._emit_loop, daemon=True)
|
|
161
|
+
self._emit_task.start()
|
|
162
|
+
|
|
163
|
+
# Emit analysis start event
|
|
164
|
+
self.emit(
|
|
165
|
+
self.EVENT_ANALYSIS_START,
|
|
166
|
+
{
|
|
167
|
+
"timestamp": datetime.utcnow().isoformat(),
|
|
168
|
+
"batch_size": self.batch_size,
|
|
169
|
+
"batch_timeout": self.batch_timeout,
|
|
170
|
+
},
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def stop(self):
|
|
174
|
+
"""Stop the event emitter and flush remaining events."""
|
|
175
|
+
# Flush remaining events
|
|
176
|
+
self._flush_events()
|
|
177
|
+
|
|
178
|
+
# Emit analysis complete event with statistics
|
|
179
|
+
self.emit(
|
|
180
|
+
self.EVENT_ANALYSIS_COMPLETE,
|
|
181
|
+
{
|
|
182
|
+
"timestamp": datetime.utcnow().isoformat(),
|
|
183
|
+
"duration": (
|
|
184
|
+
(datetime.utcnow() - self.stats["start_time"]).total_seconds()
|
|
185
|
+
if self.stats["start_time"]
|
|
186
|
+
else 0
|
|
187
|
+
),
|
|
188
|
+
"stats": self.stats,
|
|
189
|
+
},
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# Stop background task
|
|
193
|
+
self._stop_event.set()
|
|
194
|
+
if self._emit_task:
|
|
195
|
+
self._emit_task.join(timeout=1)
|
|
196
|
+
|
|
197
|
+
# Disconnect Socket.IO
|
|
198
|
+
if self.sio and self.connected:
|
|
199
|
+
self.sio.disconnect()
|
|
200
|
+
|
|
201
|
+
def emit(self, event_type: str, data: Dict[str, Any], batch: bool = False):
|
|
202
|
+
"""Emit an event, either immediately or batched.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
event_type: Type of event to emit
|
|
206
|
+
data: Event data
|
|
207
|
+
batch: Whether to batch this event
|
|
208
|
+
"""
|
|
209
|
+
event = {
|
|
210
|
+
"type": event_type,
|
|
211
|
+
"data": data,
|
|
212
|
+
"timestamp": datetime.utcnow().isoformat(),
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if batch:
|
|
216
|
+
with self.buffer_lock:
|
|
217
|
+
self.event_buffer.append(event)
|
|
218
|
+
self.stats["events_buffered"] += 1
|
|
219
|
+
|
|
220
|
+
# Check if we should flush
|
|
221
|
+
if len(self.event_buffer) >= self.batch_size:
|
|
222
|
+
self._flush_events()
|
|
223
|
+
else:
|
|
224
|
+
self._emit_event(event)
|
|
225
|
+
|
|
226
|
+
def emit_file_start(self, file_path: str, language: Optional[str] = None):
|
|
227
|
+
"""Emit file processing start event."""
|
|
228
|
+
self.emit(
|
|
229
|
+
self.EVENT_FILE_START,
|
|
230
|
+
{"file": file_path, "language": language or "unknown"},
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
def emit_file_complete(
|
|
234
|
+
self, file_path: str, nodes_count: int = 0, duration: float = 0
|
|
235
|
+
):
|
|
236
|
+
"""Emit file processing complete event."""
|
|
237
|
+
self.stats["files_processed"] += 1
|
|
238
|
+
self.emit(
|
|
239
|
+
self.EVENT_FILE_COMPLETE,
|
|
240
|
+
{"file": file_path, "nodes_count": nodes_count, "duration": duration},
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
def emit_node(self, node: CodeNodeEvent):
|
|
244
|
+
"""Emit code node discovery event (batched)."""
|
|
245
|
+
self.stats["nodes_found"] += 1
|
|
246
|
+
# In stdout mode, don't batch - emit immediately for real-time updates
|
|
247
|
+
batch_mode = not self.use_stdout
|
|
248
|
+
self.emit(self.EVENT_NODE_FOUND, node.to_dict(), batch=batch_mode)
|
|
249
|
+
|
|
250
|
+
def emit_progress(self, current: int, total: int, message: str = ""):
|
|
251
|
+
"""Emit progress update event."""
|
|
252
|
+
self.emit(
|
|
253
|
+
self.EVENT_PROGRESS,
|
|
254
|
+
{
|
|
255
|
+
"current": current,
|
|
256
|
+
"total": total,
|
|
257
|
+
"percentage": (current / total * 100) if total > 0 else 0,
|
|
258
|
+
"message": message,
|
|
259
|
+
},
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
def emit_error(self, file_path: str, error: str):
|
|
263
|
+
"""Emit error event."""
|
|
264
|
+
self.stats["errors"] += 1
|
|
265
|
+
self.emit(self.EVENT_ERROR, {"file": file_path, "error": str(error)})
|
|
266
|
+
|
|
267
|
+
def _emit_event(self, event: Dict[str, Any]):
|
|
268
|
+
"""Emit a single event."""
|
|
269
|
+
if self.use_stdout:
|
|
270
|
+
# Emit to stdout as JSON for subprocess communication
|
|
271
|
+
print(json.dumps(event), flush=True)
|
|
272
|
+
self.stats["events_sent"] += 1
|
|
273
|
+
elif self.sio and self.connected:
|
|
274
|
+
try:
|
|
275
|
+
self.sio.emit("code_tree_event", event)
|
|
276
|
+
self.stats["events_sent"] += 1
|
|
277
|
+
except Exception as e:
|
|
278
|
+
self.logger.error(f"Failed to emit event: {e}")
|
|
279
|
+
else:
|
|
280
|
+
# Fallback to logging
|
|
281
|
+
self.logger.debug(
|
|
282
|
+
f"Event: {event['type']} - {json.dumps(event['data'])[:100]}"
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
def _flush_events(self):
|
|
286
|
+
"""Flush all buffered events."""
|
|
287
|
+
with self.buffer_lock:
|
|
288
|
+
if not self.event_buffer:
|
|
289
|
+
return
|
|
290
|
+
|
|
291
|
+
# Emit as batch
|
|
292
|
+
batch_event = {
|
|
293
|
+
"type": "code:batch",
|
|
294
|
+
"data": {
|
|
295
|
+
"events": list(self.event_buffer),
|
|
296
|
+
"count": len(self.event_buffer),
|
|
297
|
+
},
|
|
298
|
+
"timestamp": datetime.utcnow().isoformat(),
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
self._emit_event(batch_event)
|
|
302
|
+
self.event_buffer.clear()
|
|
303
|
+
self.last_emit_time = time.time()
|
|
304
|
+
|
|
305
|
+
def _emit_loop(self):
|
|
306
|
+
"""Background loop for periodic event emission."""
|
|
307
|
+
while not self._stop_event.is_set():
|
|
308
|
+
time.sleep(self.batch_timeout)
|
|
309
|
+
|
|
310
|
+
# Check if enough time has passed since last emit
|
|
311
|
+
if time.time() - self.last_emit_time >= self.batch_timeout:
|
|
312
|
+
with self.buffer_lock:
|
|
313
|
+
if self.event_buffer:
|
|
314
|
+
self._flush_events()
|
|
315
|
+
|
|
316
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
317
|
+
"""Get current statistics."""
|
|
318
|
+
return self.stats.copy()
|