claude-mpm 3.3.0__py3-none-any.whl → 3.4.0__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 (58) hide show
  1. claude_mpm/agents/templates/data_engineer.json +1 -1
  2. claude_mpm/agents/templates/documentation.json +1 -1
  3. claude_mpm/agents/templates/engineer.json +1 -1
  4. claude_mpm/agents/templates/ops.json +1 -1
  5. claude_mpm/agents/templates/pm.json +1 -1
  6. claude_mpm/agents/templates/qa.json +1 -1
  7. claude_mpm/agents/templates/research.json +1 -1
  8. claude_mpm/agents/templates/security.json +1 -1
  9. claude_mpm/agents/templates/test_integration.json +112 -0
  10. claude_mpm/agents/templates/version_control.json +1 -1
  11. claude_mpm/cli/commands/memory.py +749 -26
  12. claude_mpm/cli/commands/run.py +115 -14
  13. claude_mpm/cli/parser.py +89 -1
  14. claude_mpm/constants.py +6 -0
  15. claude_mpm/core/claude_runner.py +74 -11
  16. claude_mpm/core/config.py +1 -1
  17. claude_mpm/core/session_manager.py +46 -0
  18. claude_mpm/core/simple_runner.py +74 -11
  19. claude_mpm/hooks/builtin/mpm_command_hook.py +5 -5
  20. claude_mpm/hooks/claude_hooks/hook_handler.py +213 -30
  21. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +9 -2
  22. claude_mpm/hooks/memory_integration_hook.py +51 -5
  23. claude_mpm/services/__init__.py +23 -5
  24. claude_mpm/services/agent_memory_manager.py +800 -71
  25. claude_mpm/services/memory_builder.py +823 -0
  26. claude_mpm/services/memory_optimizer.py +619 -0
  27. claude_mpm/services/memory_router.py +445 -0
  28. claude_mpm/services/project_analyzer.py +771 -0
  29. claude_mpm/services/socketio_server.py +649 -45
  30. claude_mpm/services/version_control/git_operations.py +26 -0
  31. claude_mpm-3.4.0.dist-info/METADATA +183 -0
  32. {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/RECORD +36 -52
  33. claude_mpm/agents/agent-template.yaml +0 -83
  34. claude_mpm/agents/templates/test-integration-agent.md +0 -34
  35. claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +0 -6
  36. claude_mpm/cli/README.md +0 -109
  37. claude_mpm/cli_module/refactoring_guide.md +0 -253
  38. claude_mpm/core/agent_registry.py.bak +0 -312
  39. claude_mpm/core/base_service.py.bak +0 -406
  40. claude_mpm/core/websocket_handler.py +0 -233
  41. claude_mpm/hooks/README.md +0 -97
  42. claude_mpm/orchestration/SUBPROCESS_DESIGN.md +0 -66
  43. claude_mpm/schemas/README_SECURITY.md +0 -92
  44. claude_mpm/schemas/agent_schema.json +0 -395
  45. claude_mpm/schemas/agent_schema_documentation.md +0 -181
  46. claude_mpm/schemas/agent_schema_security_notes.md +0 -165
  47. claude_mpm/schemas/examples/standard_workflow.json +0 -505
  48. claude_mpm/schemas/ticket_workflow_documentation.md +0 -482
  49. claude_mpm/schemas/ticket_workflow_schema.json +0 -590
  50. claude_mpm/services/framework_claude_md_generator/README.md +0 -92
  51. claude_mpm/services/parent_directory_manager/README.md +0 -83
  52. claude_mpm/services/version_control/VERSION +0 -1
  53. claude_mpm/services/websocket_server.py +0 -376
  54. claude_mpm-3.3.0.dist-info/METADATA +0 -432
  55. {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/WHEEL +0 -0
  56. {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/entry_points.txt +0 -0
  57. {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/licenses/LICENSE +0 -0
  58. {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/top_level.txt +0 -0
@@ -1,83 +0,0 @@
1
- # Parent Directory Manager
2
-
3
- This directory contains the modularized components of the Parent Directory Manager service.
4
-
5
- ## Module Structure
6
-
7
- The Parent Directory Manager has been refactored into specialized modules following the single-responsibility principle:
8
-
9
- ### Core Module
10
- - `__init__.py` - Main ParentDirectoryManager class that orchestrates all operations
11
-
12
- ### Specialized Modules
13
-
14
- 1. **backup_manager.py** - Handles backup operations
15
- - Creates backups of files before modifications
16
- - Manages backup retention and cleanup
17
- - Handles framework template backup protection
18
-
19
- 2. **template_deployer.py** - Manages template deployment
20
- - Handles version comparison and deployment decisions
21
- - Renders templates with variable substitution
22
- - Integrates with framework template generator
23
-
24
- 3. **framework_protector.py** - Framework protection mechanisms
25
- - Protects critical framework files
26
- - Manages protection rules and validation
27
-
28
- 4. **version_control_helper.py** - Version control integration
29
- - Git operations and version control checks
30
- - Branch management support
31
-
32
- 5. **deduplication_manager.py** - CLAUDE.md deduplication
33
- - Detects and removes duplicate CLAUDE.md files
34
- - Manages deduplication hierarchy and precedence
35
-
36
- 6. **operations.py** (formerly parent_directory_operations.py) - Directory operations
37
- - Auto-detection of parent directory contexts
38
- - Directory registration and management
39
-
40
- 7. **config_manager.py** - Configuration management
41
- - Manages directory configurations
42
- - Handles registration and configuration persistence
43
-
44
- 8. **state_manager.py** - State and lifecycle management
45
- - Initialization and cleanup operations
46
- - Operation history tracking
47
- - Logging and error handling
48
-
49
- 9. **validation_manager.py** - Validation operations
50
- - Template validation
51
- - Compatibility checking
52
- - Directory status validation
53
-
54
- 10. **version_manager.py** - Version tracking
55
- - Subsystem version management
56
- - Version compatibility validation
57
- - Version reporting
58
-
59
- ## Usage
60
-
61
- The main entry point remains the `ParentDirectoryManager` class, which delegates to these specialized modules:
62
-
63
- ```python
64
- from claude_pm.services.parent_directory_manager import ParentDirectoryManager
65
-
66
- # Initialize the manager
67
- manager = ParentDirectoryManager()
68
-
69
- # All public APIs remain the same
70
- await manager.deploy_framework_template(target_dir)
71
- ```
72
-
73
- ## Backward Compatibility
74
-
75
- A stub file at `claude_pm/services/parent_directory_manager.py` ensures backward compatibility for existing imports.
76
-
77
- ## Design Principles
78
-
79
- 1. **Single Responsibility** - Each module has a focused purpose
80
- 2. **Delegation Pattern** - Main class delegates to specialized modules
81
- 3. **Dependency Injection** - Modules receive dependencies via constructor
82
- 4. **Async Support** - All operations support async/await patterns
83
- 5. **Comprehensive Logging** - Each module uses consistent logging
@@ -1 +0,0 @@
1
- 0.9.0
@@ -1,376 +0,0 @@
1
- """WebSocket server for real-time monitoring of Claude MPM sessions."""
2
-
3
- import asyncio
4
- import json
5
- import logging
6
- import os
7
- import subprocess
8
- import threading
9
- import time
10
- from datetime import datetime
11
- from typing import Set, Dict, Any, Optional, List
12
- from collections import deque
13
-
14
- try:
15
- import websockets
16
- from websockets.server import WebSocketServerProtocol
17
- WEBSOCKETS_AVAILABLE = True
18
- except ImportError:
19
- WEBSOCKETS_AVAILABLE = False
20
- websockets = None
21
- WebSocketServerProtocol = None
22
-
23
- from ..core.logger import get_logger
24
-
25
-
26
- class WebSocketServer:
27
- """WebSocket server for broadcasting Claude MPM events."""
28
-
29
- def __init__(self, host: str = "localhost", port: int = 8765):
30
- self.host = host
31
- self.port = port
32
- self.logger = get_logger("websocket_server")
33
- self.clients: Set[WebSocketServerProtocol] = set() if WEBSOCKETS_AVAILABLE else set()
34
- self.event_history: deque = deque(maxlen=1000) # Keep last 1000 events
35
- self.server = None
36
- self.loop = None
37
- self.thread = None
38
- self.running = False
39
-
40
- # Session state
41
- self.session_id = None
42
- self.session_start = None
43
- self.claude_status = "stopped"
44
- self.claude_pid = None
45
-
46
- if not WEBSOCKETS_AVAILABLE:
47
- self.logger.warning("WebSocket support not available. Install 'websockets' package to enable.")
48
-
49
- def start(self):
50
- """Start the WebSocket server in a background thread."""
51
- if not WEBSOCKETS_AVAILABLE:
52
- self.logger.debug("WebSocket server skipped - websockets package not installed")
53
- return
54
-
55
- if self.running:
56
- return
57
-
58
- self.running = True
59
- self.thread = threading.Thread(target=self._run_server, daemon=True)
60
- self.thread.start()
61
- self.logger.info(f"WebSocket server starting on ws://{self.host}:{self.port}")
62
-
63
- def stop(self):
64
- """Stop the WebSocket server."""
65
- self.running = False
66
- if self.loop:
67
- asyncio.run_coroutine_threadsafe(self._shutdown(), self.loop)
68
- if self.thread:
69
- self.thread.join(timeout=5)
70
- self.logger.info("WebSocket server stopped")
71
-
72
- def _run_server(self):
73
- """Run the server event loop."""
74
- self.loop = asyncio.new_event_loop()
75
- asyncio.set_event_loop(self.loop)
76
-
77
- try:
78
- self.loop.run_until_complete(self._serve())
79
- except Exception as e:
80
- self.logger.error(f"WebSocket server error: {e}")
81
- finally:
82
- self.loop.close()
83
-
84
- async def _serve(self):
85
- """Start the WebSocket server."""
86
- async with websockets.serve(self._handle_client, self.host, self.port):
87
- self.logger.info(f"WebSocket server listening on ws://{self.host}:{self.port}")
88
- while self.running:
89
- await asyncio.sleep(0.1)
90
-
91
- async def _shutdown(self):
92
- """Shutdown the server."""
93
- # Close all client connections
94
- if self.clients:
95
- await asyncio.gather(
96
- *[client.close() for client in self.clients],
97
- return_exceptions=True
98
- )
99
-
100
- async def _handle_client(self, websocket: WebSocketServerProtocol, path: str):
101
- """Handle a new client connection."""
102
- self.clients.add(websocket)
103
- client_addr = websocket.remote_address
104
- self.logger.info(f"Client connected from {client_addr}")
105
-
106
- try:
107
- # Send current status
108
- await self._send_current_status(websocket)
109
-
110
- # Handle client messages
111
- async for message in websocket:
112
- try:
113
- data = json.loads(message)
114
- await self._handle_command(websocket, data)
115
- except json.JSONDecodeError:
116
- await websocket.send(json.dumps({
117
- "type": "error",
118
- "data": {"message": "Invalid JSON"}
119
- }))
120
-
121
- except websockets.exceptions.ConnectionClosed:
122
- pass
123
- finally:
124
- self.clients.remove(websocket)
125
- self.logger.info(f"Client disconnected from {client_addr}")
126
-
127
- async def _send_current_status(self, websocket: WebSocketServerProtocol):
128
- """Send current system status to a client."""
129
- status = {
130
- "type": "system.status",
131
- "timestamp": datetime.utcnow().isoformat() + "Z",
132
- "data": {
133
- "session_id": self.session_id,
134
- "session_start": self.session_start,
135
- "claude_status": self.claude_status,
136
- "claude_pid": self.claude_pid,
137
- "connected_clients": len(self.clients),
138
- "websocket_port": self.port,
139
- "instance_info": {
140
- "port": self.port,
141
- "host": self.host,
142
- "working_dir": os.getcwd() if self.session_id else None
143
- }
144
- }
145
- }
146
- await websocket.send(json.dumps(status))
147
-
148
- async def _handle_command(self, websocket: WebSocketServerProtocol, data: Dict[str, Any]):
149
- """Handle commands from clients."""
150
- command = data.get("command")
151
-
152
- if command == "get_status":
153
- await self._send_current_status(websocket)
154
-
155
- elif command == "get_history":
156
- # Send recent events
157
- params = data.get("params", {})
158
- event_types = params.get("event_types", [])
159
- limit = min(params.get("limit", 100), len(self.event_history))
160
-
161
- history = []
162
- for event in reversed(self.event_history):
163
- if not event_types or event["type"] in event_types:
164
- history.append(event)
165
- if len(history) >= limit:
166
- break
167
-
168
- await websocket.send(json.dumps({
169
- "type": "history",
170
- "data": {"events": list(reversed(history))}
171
- }))
172
-
173
- elif command == "subscribe":
174
- # For now, all clients get all events
175
- await websocket.send(json.dumps({
176
- "type": "subscribed",
177
- "data": {"channels": data.get("channels", ["*"])}
178
- }))
179
-
180
- def broadcast_event(self, event_type: str, data: Dict[str, Any]):
181
- """Broadcast an event to all connected clients."""
182
- if not WEBSOCKETS_AVAILABLE:
183
- return
184
-
185
- event = {
186
- "type": event_type,
187
- "timestamp": datetime.utcnow().isoformat() + "Z",
188
- "data": data
189
- }
190
-
191
- # Store in history
192
- self.event_history.append(event)
193
-
194
- # Broadcast to clients
195
- if self.clients and self.loop:
196
- asyncio.run_coroutine_threadsafe(
197
- self._broadcast(json.dumps(event)),
198
- self.loop
199
- )
200
-
201
- async def _broadcast(self, message: str):
202
- """Send a message to all connected clients."""
203
- if self.clients:
204
- # Send to all clients concurrently
205
- await asyncio.gather(
206
- *[client.send(message) for client in self.clients],
207
- return_exceptions=True
208
- )
209
-
210
- # Convenience methods for common events
211
-
212
- def _get_git_branch(self, working_dir: str) -> str:
213
- """Get the current git branch for the working directory."""
214
- try:
215
- result = subprocess.run(
216
- ["git", "rev-parse", "--abbrev-ref", "HEAD"],
217
- cwd=working_dir,
218
- capture_output=True,
219
- text=True,
220
- timeout=2
221
- )
222
- if result.returncode == 0:
223
- return result.stdout.strip()
224
- except Exception:
225
- pass
226
- return "not a git repo"
227
-
228
- def session_started(self, session_id: str, launch_method: str, working_dir: str):
229
- """Notify that a session has started."""
230
- self.session_id = session_id
231
- self.session_start = datetime.utcnow().isoformat() + "Z"
232
-
233
- # Get git branch if in a git repo
234
- git_branch = self._get_git_branch(working_dir)
235
-
236
- self.broadcast_event("session.start", {
237
- "session_id": session_id,
238
- "start_time": self.session_start,
239
- "launch_method": launch_method,
240
- "working_directory": working_dir,
241
- "git_branch": git_branch,
242
- "websocket_port": self.port,
243
- "instance_info": {
244
- "port": self.port,
245
- "host": self.host,
246
- "working_dir": working_dir
247
- }
248
- })
249
-
250
- def session_ended(self):
251
- """Notify that a session has ended."""
252
- if self.session_id:
253
- duration = None
254
- if self.session_start:
255
- start = datetime.fromisoformat(self.session_start.replace("Z", "+00:00"))
256
- duration = (datetime.utcnow() - start.replace(tzinfo=None)).total_seconds()
257
-
258
- self.broadcast_event("session.end", {
259
- "session_id": self.session_id,
260
- "end_time": datetime.utcnow().isoformat() + "Z",
261
- "duration_seconds": duration
262
- })
263
-
264
- self.session_id = None
265
- self.session_start = None
266
-
267
- def claude_status_changed(self, status: str, pid: Optional[int] = None, message: str = ""):
268
- """Notify Claude status change."""
269
- self.claude_status = status
270
- self.claude_pid = pid
271
- self.broadcast_event("claude.status", {
272
- "status": status,
273
- "pid": pid,
274
- "message": message
275
- })
276
-
277
- def claude_output(self, content: str, stream: str = "stdout"):
278
- """Broadcast Claude output."""
279
- self.broadcast_event("claude.output", {
280
- "content": content,
281
- "stream": stream
282
- })
283
-
284
- def agent_delegated(self, agent: str, task: str, status: str = "started"):
285
- """Notify agent delegation."""
286
- self.broadcast_event("agent.delegation", {
287
- "agent": agent,
288
- "task": task,
289
- "status": status,
290
- "timestamp": datetime.utcnow().isoformat() + "Z"
291
- })
292
-
293
- def todo_updated(self, todos: List[Dict[str, Any]]):
294
- """Notify todo list update."""
295
- stats = {
296
- "total": len(todos),
297
- "completed": sum(1 for t in todos if t.get("status") == "completed"),
298
- "in_progress": sum(1 for t in todos if t.get("status") == "in_progress"),
299
- "pending": sum(1 for t in todos if t.get("status") == "pending")
300
- }
301
-
302
- self.broadcast_event("todo.update", {
303
- "todos": todos,
304
- "stats": stats
305
- })
306
-
307
- def ticket_created(self, ticket_id: str, title: str, priority: str = "medium"):
308
- """Notify ticket creation."""
309
- self.broadcast_event("ticket.created", {
310
- "id": ticket_id,
311
- "title": title,
312
- "priority": priority,
313
- "created_at": datetime.utcnow().isoformat() + "Z"
314
- })
315
-
316
- def memory_loaded(self, agent_id: str, memory_size: int, sections_count: int):
317
- """Notify when agent memory is loaded from file."""
318
- self.broadcast_event("memory:loaded", {
319
- "agent_id": agent_id,
320
- "memory_size": memory_size,
321
- "sections_count": sections_count,
322
- "timestamp": datetime.utcnow().isoformat() + "Z"
323
- })
324
-
325
- def memory_created(self, agent_id: str, template_type: str):
326
- """Notify when new agent memory is created from template."""
327
- self.broadcast_event("memory:created", {
328
- "agent_id": agent_id,
329
- "template_type": template_type,
330
- "timestamp": datetime.utcnow().isoformat() + "Z"
331
- })
332
-
333
- def memory_updated(self, agent_id: str, learning_type: str, content: str, section: str):
334
- """Notify when learning is added to agent memory."""
335
- self.broadcast_event("memory:updated", {
336
- "agent_id": agent_id,
337
- "learning_type": learning_type,
338
- "content": content,
339
- "section": section,
340
- "timestamp": datetime.utcnow().isoformat() + "Z"
341
- })
342
-
343
- def memory_injected(self, agent_id: str, context_size: int):
344
- """Notify when agent memory is injected into context."""
345
- self.broadcast_event("memory:injected", {
346
- "agent_id": agent_id,
347
- "context_size": context_size,
348
- "timestamp": datetime.utcnow().isoformat() + "Z"
349
- })
350
-
351
-
352
- # Global instance for easy access
353
- _websocket_server: Optional[WebSocketServer] = None
354
-
355
-
356
- def get_websocket_server() -> WebSocketServer:
357
- """Get or create the global WebSocket server instance."""
358
- global _websocket_server
359
- if _websocket_server is None:
360
- _websocket_server = WebSocketServer()
361
- return _websocket_server
362
-
363
-
364
- def start_websocket_server():
365
- """Start the global WebSocket server."""
366
- server = get_websocket_server()
367
- server.start()
368
- return server
369
-
370
-
371
- def stop_websocket_server():
372
- """Stop the global WebSocket server."""
373
- global _websocket_server
374
- if _websocket_server:
375
- _websocket_server.stop()
376
- _websocket_server = None