claude-mpm 3.2.1__py3-none-any.whl → 3.3.2__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 (35) hide show
  1. claude_mpm/agents/INSTRUCTIONS.md +71 -2
  2. claude_mpm/agents/templates/data_engineer.json +1 -1
  3. claude_mpm/agents/templates/documentation.json +1 -1
  4. claude_mpm/agents/templates/engineer.json +1 -1
  5. claude_mpm/agents/templates/ops.json +1 -1
  6. claude_mpm/agents/templates/pm.json +1 -1
  7. claude_mpm/agents/templates/qa.json +1 -1
  8. claude_mpm/agents/templates/research.json +1 -1
  9. claude_mpm/agents/templates/security.json +1 -1
  10. claude_mpm/agents/templates/test_integration.json +112 -0
  11. claude_mpm/agents/templates/version_control.json +1 -1
  12. claude_mpm/cli/commands/memory.py +575 -25
  13. claude_mpm/cli/commands/run.py +115 -14
  14. claude_mpm/cli/parser.py +76 -0
  15. claude_mpm/constants.py +5 -0
  16. claude_mpm/core/claude_runner.py +13 -11
  17. claude_mpm/core/session_manager.py +46 -0
  18. claude_mpm/core/simple_runner.py +13 -11
  19. claude_mpm/hooks/claude_hooks/hook_handler.py +2 -26
  20. claude_mpm/scripts/launch_socketio_dashboard.py +261 -0
  21. claude_mpm/services/agent_memory_manager.py +264 -23
  22. claude_mpm/services/memory_builder.py +491 -0
  23. claude_mpm/services/memory_optimizer.py +619 -0
  24. claude_mpm/services/memory_router.py +445 -0
  25. claude_mpm/services/socketio_server.py +389 -1
  26. claude_mpm-3.3.2.dist-info/METADATA +159 -0
  27. {claude_mpm-3.2.1.dist-info → claude_mpm-3.3.2.dist-info}/RECORD +31 -29
  28. claude_mpm/agents/templates/test-integration-agent.md +0 -34
  29. claude_mpm/core/websocket_handler.py +0 -233
  30. claude_mpm/services/websocket_server.py +0 -376
  31. claude_mpm-3.2.1.dist-info/METADATA +0 -432
  32. {claude_mpm-3.2.1.dist-info → claude_mpm-3.3.2.dist-info}/WHEEL +0 -0
  33. {claude_mpm-3.2.1.dist-info → claude_mpm-3.3.2.dist-info}/entry_points.txt +0 -0
  34. {claude_mpm-3.2.1.dist-info → claude_mpm-3.3.2.dist-info}/licenses/LICENSE +0 -0
  35. {claude_mpm-3.2.1.dist-info → claude_mpm-3.3.2.dist-info}/top_level.txt +0 -0
@@ -254,9 +254,31 @@ class SocketIOServer:
254
254
  self.app = web.Application()
255
255
  self.sio.attach(self.app)
256
256
 
257
+ # Add CORS middleware
258
+ import aiohttp_cors
259
+ cors = aiohttp_cors.setup(self.app, defaults={
260
+ "*": aiohttp_cors.ResourceOptions(
261
+ allow_credentials=True,
262
+ expose_headers="*",
263
+ allow_headers="*",
264
+ allow_methods="*"
265
+ )
266
+ })
267
+
257
268
  # Add HTTP routes
258
269
  self.app.router.add_get('/health', self._handle_health)
259
270
  self.app.router.add_get('/status', self._handle_health)
271
+ self.app.router.add_get('/api/git-diff', self._handle_git_diff)
272
+ self.app.router.add_options('/api/git-diff', self._handle_cors_preflight)
273
+
274
+ # Add dashboard routes
275
+ self.app.router.add_get('/', self._handle_dashboard)
276
+ self.app.router.add_get('/dashboard', self._handle_dashboard)
277
+
278
+ # Add static file serving for web assets
279
+ static_path = get_project_root() / 'src' / 'claude_mpm' / 'web' / 'static'
280
+ if static_path.exists():
281
+ self.app.router.add_static('/static/', path=str(static_path), name='static')
260
282
 
261
283
  # Register event handlers
262
284
  self._register_events()
@@ -300,7 +322,335 @@ class SocketIOServer:
300
322
  "port": self.port,
301
323
  "host": self.host,
302
324
  "clients_connected": len(self.clients)
325
+ }, headers={
326
+ 'Access-Control-Allow-Origin': '*',
327
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
328
+ 'Access-Control-Allow-Headers': 'Content-Type, Accept'
303
329
  })
330
+
331
+ async def _handle_dashboard(self, request):
332
+ """Serve the dashboard HTML file."""
333
+ dashboard_path = get_project_root() / 'src' / 'claude_mpm' / 'web' / 'templates' / 'index.html'
334
+ self.logger.info(f"Dashboard requested, looking for: {dashboard_path}")
335
+ self.logger.info(f"Path exists: {dashboard_path.exists()}")
336
+ if dashboard_path.exists():
337
+ return web.FileResponse(str(dashboard_path))
338
+ else:
339
+ return web.Response(text=f"Dashboard not found at: {dashboard_path}", status=404)
340
+
341
+ async def _handle_cors_preflight(self, request):
342
+ """Handle CORS preflight requests."""
343
+ return web.Response(
344
+ status=200,
345
+ headers={
346
+ 'Access-Control-Allow-Origin': '*',
347
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
348
+ 'Access-Control-Allow-Headers': 'Content-Type, Accept, Authorization',
349
+ 'Access-Control-Max-Age': '86400'
350
+ }
351
+ )
352
+
353
+ async def _handle_git_diff(self, request):
354
+ """Handle git diff requests for file operations.
355
+
356
+ Expected query parameters:
357
+ - file: The file path to generate diff for
358
+ - timestamp: ISO timestamp of the operation (optional)
359
+ - working_dir: Working directory for git operations (optional)
360
+ """
361
+ try:
362
+ # Extract query parameters
363
+ file_path = request.query.get('file')
364
+ timestamp = request.query.get('timestamp')
365
+ working_dir = request.query.get('working_dir', os.getcwd())
366
+
367
+ self.logger.info(f"Git diff API request: file={file_path}, timestamp={timestamp}, working_dir={working_dir}")
368
+
369
+ if not file_path:
370
+ self.logger.warning("Git diff request missing file parameter")
371
+ return web.json_response({
372
+ "success": False,
373
+ "error": "Missing required parameter: file"
374
+ }, status=400, headers={
375
+ 'Access-Control-Allow-Origin': '*',
376
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
377
+ 'Access-Control-Allow-Headers': 'Content-Type, Accept'
378
+ })
379
+
380
+ self.logger.debug(f"Git diff requested for file: {file_path}, timestamp: {timestamp}")
381
+
382
+ # Generate git diff using the _generate_git_diff helper
383
+ diff_result = await self._generate_git_diff(file_path, timestamp, working_dir)
384
+
385
+ self.logger.info(f"Git diff result: success={diff_result.get('success', False)}, method={diff_result.get('method', 'unknown')}")
386
+
387
+ return web.json_response(diff_result, headers={
388
+ 'Access-Control-Allow-Origin': '*',
389
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
390
+ 'Access-Control-Allow-Headers': 'Content-Type, Accept'
391
+ })
392
+
393
+ except Exception as e:
394
+ self.logger.error(f"Error generating git diff: {e}")
395
+ import traceback
396
+ self.logger.error(f"Git diff error traceback: {traceback.format_exc()}")
397
+ return web.json_response({
398
+ "success": False,
399
+ "error": f"Failed to generate git diff: {str(e)}"
400
+ }, status=500, headers={
401
+ 'Access-Control-Allow-Origin': '*',
402
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
403
+ 'Access-Control-Allow-Headers': 'Content-Type, Accept'
404
+ })
405
+
406
+ async def _generate_git_diff(self, file_path: str, timestamp: Optional[str] = None, working_dir: str = None):
407
+ """Generate git diff for a specific file operation.
408
+
409
+ WHY: This method generates a git diff showing the changes made to a file
410
+ during a specific write operation. It uses git log and show commands to
411
+ find the most relevant commit around the specified timestamp.
412
+
413
+ Args:
414
+ file_path: Path to the file relative to the git repository
415
+ timestamp: ISO timestamp of the file operation (optional)
416
+ working_dir: Working directory containing the git repository
417
+
418
+ Returns:
419
+ dict: Contains diff content, metadata, and status information
420
+ """
421
+ try:
422
+ # If file_path is absolute, determine its git repository
423
+ if os.path.isabs(file_path):
424
+ # Find the directory containing the file
425
+ file_dir = os.path.dirname(file_path)
426
+ if os.path.exists(file_dir):
427
+ # Try to find the git root from the file's directory
428
+ current_dir = file_dir
429
+ while current_dir != "/" and current_dir:
430
+ if os.path.exists(os.path.join(current_dir, ".git")):
431
+ working_dir = current_dir
432
+ self.logger.info(f"Found git repository at: {working_dir}")
433
+ break
434
+ current_dir = os.path.dirname(current_dir)
435
+ else:
436
+ # If no git repo found, use the file's directory
437
+ working_dir = file_dir
438
+ self.logger.info(f"No git repo found, using file's directory: {working_dir}")
439
+
440
+ if working_dir is None:
441
+ working_dir = os.getcwd()
442
+
443
+ # For read-only git operations, we can work from any directory
444
+ # by passing the -C flag to git commands instead of changing directories
445
+ original_cwd = os.getcwd()
446
+ try:
447
+ # We'll use git -C <working_dir> for all commands instead of chdir
448
+
449
+ # Check if this is a git repository
450
+ git_check = await asyncio.create_subprocess_exec(
451
+ 'git', '-C', working_dir, 'rev-parse', '--git-dir',
452
+ stdout=asyncio.subprocess.PIPE,
453
+ stderr=asyncio.subprocess.PIPE
454
+ )
455
+ await git_check.communicate()
456
+
457
+ if git_check.returncode != 0:
458
+ return {
459
+ "success": False,
460
+ "error": "Not a git repository",
461
+ "file_path": file_path,
462
+ "working_dir": working_dir
463
+ }
464
+
465
+ # Get the absolute path of the file relative to git root
466
+ git_root_proc = await asyncio.create_subprocess_exec(
467
+ 'git', '-C', working_dir, 'rev-parse', '--show-toplevel',
468
+ stdout=asyncio.subprocess.PIPE,
469
+ stderr=asyncio.subprocess.PIPE
470
+ )
471
+ git_root_output, _ = await git_root_proc.communicate()
472
+
473
+ if git_root_proc.returncode != 0:
474
+ return {"success": False, "error": "Failed to determine git root directory"}
475
+
476
+ git_root = git_root_output.decode().strip()
477
+
478
+ # Make file_path relative to git root if it's absolute
479
+ if os.path.isabs(file_path):
480
+ try:
481
+ file_path = os.path.relpath(file_path, git_root)
482
+ except ValueError:
483
+ # File is not under git root
484
+ pass
485
+
486
+ # If timestamp is provided, try to find commits around that time
487
+ if timestamp:
488
+ # Convert timestamp to git format
489
+ try:
490
+ from datetime import datetime
491
+ dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
492
+ git_since = dt.strftime('%Y-%m-%d %H:%M:%S')
493
+
494
+ # Find commits that modified this file around the timestamp
495
+ log_proc = await asyncio.create_subprocess_exec(
496
+ 'git', '-C', working_dir, 'log', '--oneline', '--since', git_since,
497
+ '--until', f'{git_since} +1 hour', '--', file_path,
498
+ stdout=asyncio.subprocess.PIPE,
499
+ stderr=asyncio.subprocess.PIPE
500
+ )
501
+ log_output, _ = await log_proc.communicate()
502
+
503
+ if log_proc.returncode == 0 and log_output:
504
+ # Get the most recent commit hash
505
+ commits = log_output.decode().strip().split('\n')
506
+ if commits and commits[0]:
507
+ commit_hash = commits[0].split()[0]
508
+
509
+ # Get the diff for this specific commit
510
+ diff_proc = await asyncio.create_subprocess_exec(
511
+ 'git', '-C', working_dir, 'show', '--format=fuller', commit_hash, '--', file_path,
512
+ stdout=asyncio.subprocess.PIPE,
513
+ stderr=asyncio.subprocess.PIPE
514
+ )
515
+ diff_output, diff_error = await diff_proc.communicate()
516
+
517
+ if diff_proc.returncode == 0:
518
+ return {
519
+ "success": True,
520
+ "diff": diff_output.decode(),
521
+ "commit_hash": commit_hash,
522
+ "file_path": file_path,
523
+ "method": "timestamp_based",
524
+ "timestamp": timestamp
525
+ }
526
+ except Exception as e:
527
+ self.logger.warning(f"Failed to parse timestamp or find commits: {e}")
528
+
529
+ # Fallback: Get the most recent change to the file
530
+ log_proc = await asyncio.create_subprocess_exec(
531
+ 'git', '-C', working_dir, 'log', '-1', '--oneline', '--', file_path,
532
+ stdout=asyncio.subprocess.PIPE,
533
+ stderr=asyncio.subprocess.PIPE
534
+ )
535
+ log_output, _ = await log_proc.communicate()
536
+
537
+ if log_proc.returncode == 0 and log_output:
538
+ commit_hash = log_output.decode().strip().split()[0]
539
+
540
+ # Get the diff for the most recent commit
541
+ diff_proc = await asyncio.create_subprocess_exec(
542
+ 'git', '-C', working_dir, 'show', '--format=fuller', commit_hash, '--', file_path,
543
+ stdout=asyncio.subprocess.PIPE,
544
+ stderr=asyncio.subprocess.PIPE
545
+ )
546
+ diff_output, diff_error = await diff_proc.communicate()
547
+
548
+ if diff_proc.returncode == 0:
549
+ return {
550
+ "success": True,
551
+ "diff": diff_output.decode(),
552
+ "commit_hash": commit_hash,
553
+ "file_path": file_path,
554
+ "method": "latest_commit",
555
+ "timestamp": timestamp
556
+ }
557
+
558
+ # Try to show unstaged changes first
559
+ diff_proc = await asyncio.create_subprocess_exec(
560
+ 'git', '-C', working_dir, 'diff', '--', file_path,
561
+ stdout=asyncio.subprocess.PIPE,
562
+ stderr=asyncio.subprocess.PIPE
563
+ )
564
+ diff_output, _ = await diff_proc.communicate()
565
+
566
+ if diff_proc.returncode == 0 and diff_output.decode().strip():
567
+ return {
568
+ "success": True,
569
+ "diff": diff_output.decode(),
570
+ "commit_hash": "unstaged_changes",
571
+ "file_path": file_path,
572
+ "method": "unstaged_changes",
573
+ "timestamp": timestamp
574
+ }
575
+
576
+ # Then try staged changes
577
+ diff_proc = await asyncio.create_subprocess_exec(
578
+ 'git', '-C', working_dir, 'diff', '--cached', '--', file_path,
579
+ stdout=asyncio.subprocess.PIPE,
580
+ stderr=asyncio.subprocess.PIPE
581
+ )
582
+ diff_output, _ = await diff_proc.communicate()
583
+
584
+ if diff_proc.returncode == 0 and diff_output.decode().strip():
585
+ return {
586
+ "success": True,
587
+ "diff": diff_output.decode(),
588
+ "commit_hash": "staged_changes",
589
+ "file_path": file_path,
590
+ "method": "staged_changes",
591
+ "timestamp": timestamp
592
+ }
593
+
594
+ # Final fallback: Show changes against HEAD
595
+ diff_proc = await asyncio.create_subprocess_exec(
596
+ 'git', '-C', working_dir, 'diff', 'HEAD', '--', file_path,
597
+ stdout=asyncio.subprocess.PIPE,
598
+ stderr=asyncio.subprocess.PIPE
599
+ )
600
+ diff_output, _ = await diff_proc.communicate()
601
+
602
+ if diff_proc.returncode == 0:
603
+ working_diff = diff_output.decode()
604
+ if working_diff.strip():
605
+ return {
606
+ "success": True,
607
+ "diff": working_diff,
608
+ "commit_hash": "working_directory",
609
+ "file_path": file_path,
610
+ "method": "working_directory",
611
+ "timestamp": timestamp
612
+ }
613
+
614
+ # Check if file is from a different repository
615
+ suggestions = [
616
+ "The file may not be tracked by git",
617
+ "The file may not have any committed changes",
618
+ "The timestamp may be outside the git history range"
619
+ ]
620
+
621
+ if os.path.isabs(file_path) and not file_path.startswith(os.getcwd()):
622
+ current_repo = os.path.basename(os.getcwd())
623
+ file_repo = "unknown"
624
+ # Try to extract repository name from path
625
+ path_parts = file_path.split("/")
626
+ if "Projects" in path_parts:
627
+ idx = path_parts.index("Projects")
628
+ if idx + 1 < len(path_parts):
629
+ file_repo = path_parts[idx + 1]
630
+
631
+ suggestions.clear()
632
+ suggestions.append(f"This file is from the '{file_repo}' repository")
633
+ suggestions.append(f"The git diff viewer is running from the '{current_repo}' repository")
634
+ suggestions.append("Git diff can only show changes for files in the current repository")
635
+ suggestions.append("To view changes for this file, run the monitoring dashboard from its repository")
636
+
637
+ return {
638
+ "success": False,
639
+ "error": "No git history found for this file",
640
+ "file_path": file_path,
641
+ "suggestions": suggestions
642
+ }
643
+
644
+ finally:
645
+ os.chdir(original_cwd)
646
+
647
+ except Exception as e:
648
+ self.logger.error(f"Error in _generate_git_diff: {e}")
649
+ return {
650
+ "success": False,
651
+ "error": f"Git diff generation failed: {str(e)}",
652
+ "file_path": file_path
653
+ }
304
654
 
305
655
 
306
656
  def _register_events(self):
@@ -406,6 +756,44 @@ class SocketIOServer:
406
756
 
407
757
  # Re-broadcast to all other clients
408
758
  await self.sio.emit('claude_event', data, skip_sid=sid)
759
+
760
+ @self.sio.event
761
+ async def get_git_branch(sid, working_dir=None):
762
+ """Get the current git branch for a directory"""
763
+ import subprocess
764
+ try:
765
+ if not working_dir:
766
+ working_dir = os.getcwd()
767
+
768
+ # Run git command to get current branch
769
+ result = subprocess.run(
770
+ ["git", "rev-parse", "--abbrev-ref", "HEAD"],
771
+ cwd=working_dir,
772
+ capture_output=True,
773
+ text=True
774
+ )
775
+
776
+ if result.returncode == 0:
777
+ branch = result.stdout.strip()
778
+ await self.sio.emit('git_branch_response', {
779
+ 'success': True,
780
+ 'branch': branch,
781
+ 'working_dir': working_dir
782
+ }, room=sid)
783
+ else:
784
+ await self.sio.emit('git_branch_response', {
785
+ 'success': False,
786
+ 'error': 'Not a git repository',
787
+ 'working_dir': working_dir
788
+ }, room=sid)
789
+
790
+ except Exception as e:
791
+ self.logger.error(f"Error getting git branch: {e}")
792
+ await self.sio.emit('git_branch_response', {
793
+ 'success': False,
794
+ 'error': str(e),
795
+ 'working_dir': working_dir
796
+ }, room=sid)
409
797
 
410
798
  async def _send_current_status(self, sid: str):
411
799
  """Send current system status to a client."""
@@ -695,4 +1083,4 @@ def stop_socketio_server():
695
1083
  global _socketio_server
696
1084
  if _socketio_server:
697
1085
  _socketio_server.stop()
698
- _socketio_server = None
1086
+ _socketio_server = None
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.4
2
+ Name: claude-mpm
3
+ Version: 3.3.2
4
+ Summary: Claude Multi-agent Project Manager - Clean orchestration with ticket management
5
+ Home-page: https://github.com/bobmatnyc/claude-mpm
6
+ Author: Claude MPM Team
7
+ Author-email: bob@matsuoka.com
8
+ License: MIT
9
+ Keywords: claude,orchestration,multi-agent,ticket-management
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: ai-trackdown-pytools>=1.4.0
23
+ Requires-Dist: pyyaml>=6.0
24
+ Requires-Dist: python-dotenv>=0.19.0
25
+ Requires-Dist: rich>=13.0.0
26
+ Requires-Dist: click>=8.0.0
27
+ Requires-Dist: pexpect>=4.8.0
28
+ Requires-Dist: psutil>=5.9.0
29
+ Requires-Dist: requests>=2.25.0
30
+ Requires-Dist: flask>=3.0.0
31
+ Requires-Dist: flask-cors>=4.0.0
32
+ Requires-Dist: watchdog>=3.0.0
33
+ Requires-Dist: tree-sitter>=0.21.0
34
+ Requires-Dist: tree-sitter-language-pack>=0.8.0
35
+ Provides-Extra: dev
36
+ Requires-Dist: pytest>=7.0; extra == "dev"
37
+ Requires-Dist: pytest-asyncio; extra == "dev"
38
+ Requires-Dist: pytest-cov; extra == "dev"
39
+ Requires-Dist: black; extra == "dev"
40
+ Requires-Dist: flake8; extra == "dev"
41
+ Requires-Dist: mypy; extra == "dev"
42
+ Provides-Extra: monitor
43
+ Requires-Dist: python-socketio>=5.11.0; extra == "monitor"
44
+ Requires-Dist: aiohttp>=3.9.0; extra == "monitor"
45
+ Requires-Dist: python-engineio>=4.8.0; extra == "monitor"
46
+ Dynamic: author-email
47
+ Dynamic: home-page
48
+ Dynamic: license-file
49
+ Dynamic: requires-python
50
+
51
+ # Claude MPM - Multi-Agent Project Manager
52
+
53
+ A powerful orchestration framework for Claude Code that enables multi-agent workflows, session management, and real-time monitoring through an intuitive interface.
54
+
55
+ > **Quick Start**: See [QUICKSTART.md](QUICKSTART.md) to get running in 5 minutes!
56
+
57
+ ## Features
58
+
59
+ - 🤖 **Multi-Agent System**: Automatically delegates tasks to specialized agents (PM, Research, Engineer, QA, Documentation, Security, Ops, Data Engineer)
60
+ - 🔄 **Session Management**: Resume previous sessions with `--resume`
61
+ - 📊 **Real-Time Monitoring**: Live dashboard with `--monitor` flag
62
+ - 📁 **Multi-Project Support**: Per-session working directories
63
+ - 🔍 **Git Integration**: View diffs and track changes across projects
64
+ - 🎯 **Smart Task Orchestration**: PM agent intelligently routes work to specialists
65
+
66
+ ## Installation
67
+
68
+ ```bash
69
+ # Install from PyPI
70
+ pip install claude-mpm
71
+
72
+ # Or with monitoring support
73
+ pip install "claude-mpm[monitor]"
74
+ ```
75
+
76
+ ## Basic Usage
77
+
78
+ ```bash
79
+ # Interactive mode (recommended)
80
+ claude-mpm
81
+
82
+ # Non-interactive with task
83
+ claude-mpm run -i "analyze this codebase" --non-interactive
84
+
85
+ # With monitoring dashboard
86
+ claude-mpm run --monitor
87
+
88
+ # Resume last session
89
+ claude-mpm run --resume
90
+ ```
91
+
92
+ For detailed usage, see [QUICKSTART.md](QUICKSTART.md)
93
+
94
+ ## Key Capabilities
95
+
96
+ ### Multi-Agent Orchestration
97
+ The PM agent automatically delegates work to specialized agents:
98
+ - **Research**: Codebase analysis and investigation
99
+ - **Engineer**: Implementation and coding
100
+ - **QA**: Testing and validation
101
+ - **Documentation**: Docs and guides
102
+ - **Security**: Security analysis
103
+ - **Ops**: Deployment and infrastructure
104
+
105
+ ### Session Management
106
+ - All work is tracked in persistent sessions
107
+ - Resume any session with `--resume`
108
+ - Switch between projects with per-session directories
109
+ - View session history and activity
110
+
111
+ ### Real-Time Monitoring
112
+ The `--monitor` flag opens a web dashboard showing:
113
+ - Live agent activity and delegations
114
+ - File operations with git diff viewer
115
+ - Tool usage and results
116
+ - Session management UI
117
+
118
+ See [docs/monitoring.md](docs/monitoring.md) for full monitoring guide.
119
+
120
+
121
+ ## Documentation
122
+
123
+ - **[Quick Start Guide](QUICKSTART.md)** - Get running in 5 minutes
124
+ - **[Monitoring Dashboard](docs/monitoring.md)** - Real-time monitoring features
125
+ - **[Project Structure](docs/STRUCTURE.md)** - Codebase organization
126
+ - **[Deployment Guide](docs/DEPLOY.md)** - Publishing and versioning
127
+ - **[User Guide](docs/user/)** - Detailed usage documentation
128
+ - **[Developer Guide](docs/developer/)** - Architecture and API reference
129
+
130
+ ## Recent Updates (v3.3.1)
131
+
132
+ ### Session Working Directories
133
+ - Each session can now have its own working directory
134
+ - Git operations use the session's directory automatically
135
+ - Switch projects seamlessly without changing terminal directory
136
+
137
+ ### Monitoring Improvements
138
+ - Fixed git diff viewer for cross-project files
139
+ - Auto-sync working directory from session data
140
+ - Improved error handling and display
141
+
142
+ See [CHANGELOG.md](CHANGELOG.md) for full history.
143
+
144
+ ## Development
145
+
146
+ ### Contributing
147
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
148
+
149
+ ### Project Structure
150
+ See [docs/STRUCTURE.md](docs/STRUCTURE.md) for codebase organization.
151
+
152
+ ### License
153
+ MIT License - see [LICENSE](LICENSE) file.
154
+
155
+ ## Credits
156
+
157
+ - Based on [claude-multiagent-pm](https://github.com/kfsone/claude-multiagent-pm)
158
+ - Enhanced for [Claude Code](https://docs.anthropic.com/en/docs/claude-code) integration
159
+ - Built with ❤️ by the Claude MPM community