claude-mpm 3.3.2__py3-none-any.whl → 3.4.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 (48) hide show
  1. claude_mpm/cli/commands/memory.py +192 -14
  2. claude_mpm/cli/parser.py +13 -1
  3. claude_mpm/constants.py +1 -0
  4. claude_mpm/core/claude_runner.py +61 -0
  5. claude_mpm/core/config.py +161 -1
  6. claude_mpm/core/simple_runner.py +61 -0
  7. claude_mpm/hooks/builtin/mpm_command_hook.py +5 -5
  8. claude_mpm/hooks/claude_hooks/hook_handler.py +211 -4
  9. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +10 -3
  10. claude_mpm/hooks/memory_integration_hook.py +51 -5
  11. claude_mpm/scripts/socketio_daemon.py +49 -9
  12. claude_mpm/scripts/socketio_server_manager.py +370 -45
  13. claude_mpm/services/__init__.py +41 -5
  14. claude_mpm/services/agent_memory_manager.py +541 -51
  15. claude_mpm/services/exceptions.py +677 -0
  16. claude_mpm/services/health_monitor.py +892 -0
  17. claude_mpm/services/memory_builder.py +341 -7
  18. claude_mpm/services/memory_optimizer.py +6 -2
  19. claude_mpm/services/project_analyzer.py +771 -0
  20. claude_mpm/services/recovery_manager.py +670 -0
  21. claude_mpm/services/socketio_server.py +653 -36
  22. claude_mpm/services/standalone_socketio_server.py +703 -34
  23. claude_mpm/services/version_control/git_operations.py +26 -0
  24. {claude_mpm-3.3.2.dist-info → claude_mpm-3.4.2.dist-info}/METADATA +34 -10
  25. {claude_mpm-3.3.2.dist-info → claude_mpm-3.4.2.dist-info}/RECORD +30 -44
  26. claude_mpm/agents/agent-template.yaml +0 -83
  27. claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +0 -6
  28. claude_mpm/cli/README.md +0 -109
  29. claude_mpm/cli_module/refactoring_guide.md +0 -253
  30. claude_mpm/core/agent_registry.py.bak +0 -312
  31. claude_mpm/core/base_service.py.bak +0 -406
  32. claude_mpm/hooks/README.md +0 -97
  33. claude_mpm/orchestration/SUBPROCESS_DESIGN.md +0 -66
  34. claude_mpm/schemas/README_SECURITY.md +0 -92
  35. claude_mpm/schemas/agent_schema.json +0 -395
  36. claude_mpm/schemas/agent_schema_documentation.md +0 -181
  37. claude_mpm/schemas/agent_schema_security_notes.md +0 -165
  38. claude_mpm/schemas/examples/standard_workflow.json +0 -505
  39. claude_mpm/schemas/ticket_workflow_documentation.md +0 -482
  40. claude_mpm/schemas/ticket_workflow_schema.json +0 -590
  41. claude_mpm/services/framework_claude_md_generator/README.md +0 -92
  42. claude_mpm/services/parent_directory_manager/README.md +0 -83
  43. claude_mpm/services/version_control/VERSION +0 -1
  44. /claude_mpm/{web → dashboard}/open_dashboard.py +0 -0
  45. {claude_mpm-3.3.2.dist-info → claude_mpm-3.4.2.dist-info}/WHEEL +0 -0
  46. {claude_mpm-3.3.2.dist-info → claude_mpm-3.4.2.dist-info}/entry_points.txt +0 -0
  47. {claude_mpm-3.3.2.dist-info → claude_mpm-3.4.2.dist-info}/licenses/LICENSE +0 -0
  48. {claude_mpm-3.3.2.dist-info → claude_mpm-3.4.2.dist-info}/top_level.txt +0 -0
@@ -23,6 +23,7 @@ while preserving essential information.
23
23
  """
24
24
 
25
25
  import re
26
+ import os
26
27
  from pathlib import Path
27
28
  from typing import Dict, List, Optional, Any, Tuple
28
29
  from datetime import datetime
@@ -31,6 +32,7 @@ from claude_mpm.core import LoggerMixin
31
32
  from claude_mpm.core.config import Config
32
33
  from claude_mpm.utils.paths import PathResolver
33
34
  from claude_mpm.services.memory_router import MemoryRouter
35
+ from claude_mpm.services.project_analyzer import ProjectAnalyzer
34
36
 
35
37
 
36
38
  class MemoryBuilder(LoggerMixin):
@@ -98,17 +100,110 @@ class MemoryBuilder(LoggerMixin):
98
100
  ]
99
101
  }
100
102
 
101
- def __init__(self, config: Optional[Config] = None):
103
+ def __init__(self, config: Optional[Config] = None, working_directory: Optional[Path] = None):
102
104
  """Initialize the memory builder.
103
105
 
104
106
  Args:
105
107
  config: Optional Config object
108
+ working_directory: Optional working directory for project-specific analysis
106
109
  """
107
110
  super().__init__()
108
111
  self.config = config or Config()
109
112
  self.project_root = PathResolver.get_project_root()
110
- self.memories_dir = self.project_root / ".claude-mpm" / "memories"
113
+ # Use current working directory by default, not project root
114
+ self.working_directory = working_directory or Path(os.getcwd())
115
+ self.memories_dir = self.working_directory / ".claude-mpm" / "memories"
111
116
  self.router = MemoryRouter(config)
117
+ self.project_analyzer = ProjectAnalyzer(config, self.working_directory)
118
+
119
+ def _get_dynamic_doc_files(self) -> Dict[str, Dict[str, Any]]:
120
+ """Get documentation files to process based on project analysis.
121
+
122
+ WHY: Instead of hardcoded file list, dynamically discover important files
123
+ based on actual project structure and characteristics.
124
+
125
+ Returns:
126
+ Dict mapping file paths to processing configuration
127
+ """
128
+ dynamic_files = {}
129
+
130
+ # Start with static important files
131
+ static_files = self.DOC_FILES.copy()
132
+
133
+ # Get project-specific important files
134
+ try:
135
+ important_files = self.project_analyzer.get_important_files_for_context()
136
+ project_characteristics = self.project_analyzer.analyze_project()
137
+
138
+ # Add configuration files
139
+ for config_file in project_characteristics.important_configs:
140
+ if config_file not in static_files:
141
+ file_ext = Path(config_file).suffix.lower()
142
+
143
+ if file_ext in ['.json', '.toml', '.yaml', '.yml']:
144
+ dynamic_files[config_file] = {
145
+ 'priority': 'medium',
146
+ 'sections': ['configuration', 'setup', 'dependencies'],
147
+ 'agents': ['engineer', 'pm'],
148
+ 'file_type': 'config'
149
+ }
150
+
151
+ # Add project-specific documentation
152
+ for doc_file in important_files:
153
+ if doc_file not in static_files and doc_file not in dynamic_files:
154
+ file_path = Path(doc_file)
155
+
156
+ # Determine processing config based on file name/path
157
+ if 'api' in doc_file.lower() or 'endpoint' in doc_file.lower():
158
+ dynamic_files[doc_file] = {
159
+ 'priority': 'high',
160
+ 'sections': ['api', 'endpoints', 'integration'],
161
+ 'agents': ['engineer', 'integration'],
162
+ 'file_type': 'api_doc'
163
+ }
164
+ elif 'architecture' in doc_file.lower() or 'design' in doc_file.lower():
165
+ dynamic_files[doc_file] = {
166
+ 'priority': 'high',
167
+ 'sections': ['architecture', 'design', 'patterns'],
168
+ 'agents': ['engineer', 'architect'],
169
+ 'file_type': 'architecture'
170
+ }
171
+ elif 'test' in doc_file.lower():
172
+ dynamic_files[doc_file] = {
173
+ 'priority': 'medium',
174
+ 'sections': ['testing', 'quality'],
175
+ 'agents': ['qa', 'engineer'],
176
+ 'file_type': 'test_doc'
177
+ }
178
+ elif file_path.suffix.lower() == '.md':
179
+ # Generic markdown file
180
+ dynamic_files[doc_file] = {
181
+ 'priority': 'low',
182
+ 'sections': ['documentation', 'guidelines'],
183
+ 'agents': ['pm', 'engineer'],
184
+ 'file_type': 'markdown'
185
+ }
186
+
187
+ # Add key source files for pattern analysis (limited selection)
188
+ if project_characteristics.entry_points:
189
+ for entry_point in project_characteristics.entry_points[:2]: # Only first 2
190
+ if entry_point not in dynamic_files:
191
+ dynamic_files[entry_point] = {
192
+ 'priority': 'low',
193
+ 'sections': ['patterns', 'implementation'],
194
+ 'agents': ['engineer'],
195
+ 'file_type': 'source',
196
+ 'extract_patterns_only': True # Only extract patterns, not full content
197
+ }
198
+
199
+ except Exception as e:
200
+ self.logger.warning(f"Error getting dynamic doc files: {e}")
201
+
202
+ # Merge static and dynamic files
203
+ all_files = {**static_files, **dynamic_files}
204
+
205
+ self.logger.debug(f"Processing {len(all_files)} documentation files ({len(static_files)} static, {len(dynamic_files)} dynamic)")
206
+ return all_files
112
207
 
113
208
  def build_from_documentation(self, force_rebuild: bool = False) -> Dict[str, Any]:
114
209
  """Build agent memories from project documentation.
@@ -134,8 +229,11 @@ class MemoryBuilder(LoggerMixin):
134
229
  "errors": []
135
230
  }
136
231
 
232
+ # Get dynamic list of files to process
233
+ doc_files = self._get_dynamic_doc_files()
234
+
137
235
  # Process each documentation file
138
- for doc_path, doc_config in self.DOC_FILES.items():
236
+ for doc_path, doc_config in doc_files.items():
139
237
  file_path = self.project_root / doc_path
140
238
 
141
239
  if not file_path.exists():
@@ -292,8 +390,236 @@ class MemoryBuilder(LoggerMixin):
292
390
  "error": str(e)
293
391
  }
294
392
 
393
+ def _extract_from_config_file(self, content: str, file_path: Path, doc_config: Dict[str, Any]) -> List[Dict[str, Any]]:
394
+ """Extract memory-worthy information from configuration files.
395
+
396
+ WHY: Configuration files contain important setup patterns, dependencies,
397
+ and architectural decisions that agents should understand.
398
+
399
+ Args:
400
+ content: File content
401
+ file_path: Path to the file
402
+ doc_config: Processing configuration
403
+
404
+ Returns:
405
+ List of extracted memory items
406
+ """
407
+ extracted_items = []
408
+ source = str(file_path.relative_to(self.project_root))
409
+
410
+ try:
411
+ file_ext = file_path.suffix.lower()
412
+
413
+ if file_ext == '.json':
414
+ # Parse JSON configuration
415
+ import json
416
+ config_data = json.loads(content)
417
+ items = self._extract_from_json_config(config_data, source)
418
+ extracted_items.extend(items)
419
+
420
+ elif file_ext in ['.toml']:
421
+ # Parse TOML configuration
422
+ try:
423
+ try:
424
+ import tomllib
425
+ except ImportError:
426
+ import tomli as tomllib
427
+ with open(file_path, 'rb') as f:
428
+ config_data = tomllib.load(f)
429
+ items = self._extract_from_toml_config(config_data, source)
430
+ extracted_items.extend(items)
431
+ except ImportError:
432
+ self.logger.warning(f"TOML parsing not available for {source}")
433
+
434
+ elif file_ext in ['.yaml', '.yml']:
435
+ # For YAML, fall back to text-based extraction for now
436
+ items = self.extract_from_text(content, source)
437
+ extracted_items.extend(items)
438
+
439
+ # Also extract text patterns for comments and documentation
440
+ text_items = self.extract_from_text(content, source)
441
+ extracted_items.extend(text_items)
442
+
443
+ except Exception as e:
444
+ self.logger.warning(f"Error parsing config file {source}: {e}")
445
+ # Fall back to text extraction
446
+ extracted_items = self.extract_from_text(content, source)
447
+
448
+ return extracted_items
449
+
450
+ def _extract_from_json_config(self, config_data: dict, source: str) -> List[Dict[str, Any]]:
451
+ """Extract patterns from JSON configuration."""
452
+ items = []
453
+
454
+ # Extract dependencies information
455
+ if 'dependencies' in config_data:
456
+ deps = config_data['dependencies']
457
+ if isinstance(deps, dict) and deps:
458
+ dep_names = list(deps.keys())[:5] # Limit to prevent overwhelming
459
+ deps_str = ", ".join(dep_names)
460
+ items.append({
461
+ "content": f"Key dependencies: {deps_str}",
462
+ "type": "dependency_info",
463
+ "source": source,
464
+ "target_agent": "engineer",
465
+ "section": "Current Technical Context",
466
+ "confidence": 0.8
467
+ })
468
+
469
+ # Extract scripts (for package.json)
470
+ if 'scripts' in config_data:
471
+ scripts = config_data['scripts']
472
+ if isinstance(scripts, dict):
473
+ for script_name, script_cmd in list(scripts.items())[:3]: # Limit to first 3
474
+ items.append({
475
+ "content": f"Build script '{script_name}': {script_cmd[:50]}{'...' if len(script_cmd) > 50 else ''}",
476
+ "type": "build_pattern",
477
+ "source": source,
478
+ "target_agent": "engineer",
479
+ "section": "Implementation Guidelines",
480
+ "confidence": 0.7
481
+ })
482
+
483
+ return items
484
+
485
+ def _extract_from_toml_config(self, config_data: dict, source: str) -> List[Dict[str, Any]]:
486
+ """Extract patterns from TOML configuration."""
487
+ items = []
488
+
489
+ # Extract project metadata (for pyproject.toml)
490
+ if 'project' in config_data:
491
+ project_info = config_data['project']
492
+ if 'dependencies' in project_info:
493
+ deps = project_info['dependencies']
494
+ if deps:
495
+ items.append({
496
+ "content": f"Python dependencies: {', '.join(deps[:5])}",
497
+ "type": "dependency_info",
498
+ "source": source,
499
+ "target_agent": "engineer",
500
+ "section": "Current Technical Context",
501
+ "confidence": 0.8
502
+ })
503
+
504
+ # Extract Rust dependencies (for Cargo.toml)
505
+ if 'dependencies' in config_data:
506
+ deps = config_data['dependencies']
507
+ if isinstance(deps, dict) and deps:
508
+ dep_names = list(deps.keys())[:5]
509
+ items.append({
510
+ "content": f"Rust dependencies: {', '.join(dep_names)}",
511
+ "type": "dependency_info",
512
+ "source": source,
513
+ "target_agent": "engineer",
514
+ "section": "Current Technical Context",
515
+ "confidence": 0.8
516
+ })
517
+
518
+ return items
519
+
520
+ def _extract_from_source_file(self, content: str, file_path: Path, doc_config: Dict[str, Any]) -> List[Dict[str, Any]]:
521
+ """Extract patterns from source code files.
522
+
523
+ WHY: Source files contain implementation patterns and architectural
524
+ decisions that agents should be aware of, but we only extract high-level
525
+ patterns rather than detailed code analysis.
526
+
527
+ Args:
528
+ content: File content
529
+ file_path: Path to the file
530
+ doc_config: Processing configuration
531
+
532
+ Returns:
533
+ List of extracted memory items
534
+ """
535
+ extracted_items = []
536
+ source = str(file_path.relative_to(self.project_root))
537
+
538
+ # Only extract patterns if specified
539
+ if not doc_config.get('extract_patterns_only', False):
540
+ return []
541
+
542
+ file_ext = file_path.suffix.lower()
543
+
544
+ # Language-specific pattern extraction
545
+ if file_ext == '.py':
546
+ items = self._extract_python_patterns(content, source)
547
+ extracted_items.extend(items)
548
+ elif file_ext in ['.js', '.ts']:
549
+ items = self._extract_javascript_patterns(content, source)
550
+ extracted_items.extend(items)
551
+
552
+ return extracted_items[:3] # Limit to prevent overwhelming
553
+
554
+ def _extract_python_patterns(self, content: str, source: str) -> List[Dict[str, Any]]:
555
+ """Extract high-level patterns from Python source."""
556
+ items = []
557
+
558
+ # Check for common patterns
559
+ if 'if __name__ == "__main__"' in content:
560
+ items.append({
561
+ "content": "Uses if __name__ == '__main__' pattern for script execution",
562
+ "type": "pattern",
563
+ "source": source,
564
+ "target_agent": "engineer",
565
+ "section": "Coding Patterns Learned",
566
+ "confidence": 0.8
567
+ })
568
+
569
+ if 'from pathlib import Path' in content:
570
+ items.append({
571
+ "content": "Uses pathlib.Path for file operations (recommended pattern)",
572
+ "type": "pattern",
573
+ "source": source,
574
+ "target_agent": "engineer",
575
+ "section": "Coding Patterns Learned",
576
+ "confidence": 0.7
577
+ })
578
+
579
+ # Check for class definitions
580
+ class_matches = re.findall(r'class\s+(\w+)', content)
581
+ if class_matches:
582
+ items.append({
583
+ "content": f"Defines classes: {', '.join(class_matches[:3])}",
584
+ "type": "architecture",
585
+ "source": source,
586
+ "target_agent": "engineer",
587
+ "section": "Project Architecture",
588
+ "confidence": 0.6
589
+ })
590
+
591
+ return items
592
+
593
+ def _extract_javascript_patterns(self, content: str, source: str) -> List[Dict[str, Any]]:
594
+ """Extract high-level patterns from JavaScript/TypeScript source."""
595
+ items = []
596
+
597
+ # Check for async patterns
598
+ if 'async function' in content or 'async ' in content:
599
+ items.append({
600
+ "content": "Uses async/await patterns for asynchronous operations",
601
+ "type": "pattern",
602
+ "source": source,
603
+ "target_agent": "engineer",
604
+ "section": "Coding Patterns Learned",
605
+ "confidence": 0.8
606
+ })
607
+
608
+ # Check for module patterns
609
+ if 'export ' in content:
610
+ items.append({
611
+ "content": "Uses ES6 module export patterns",
612
+ "type": "pattern",
613
+ "source": source,
614
+ "target_agent": "engineer",
615
+ "section": "Coding Patterns Learned",
616
+ "confidence": 0.7
617
+ })
618
+
619
+ return items
620
+
295
621
  def _process_documentation_file(self, file_path: Path, doc_config: Dict[str, Any]) -> Dict[str, Any]:
296
- """Process a single documentation file.
622
+ """Process a single documentation file with enhanced file type support.
297
623
 
298
624
  Args:
299
625
  file_path: Path to documentation file
@@ -304,10 +630,18 @@ class MemoryBuilder(LoggerMixin):
304
630
  """
305
631
  try:
306
632
  # Read file content
307
- content = file_path.read_text(encoding='utf-8')
633
+ content = file_path.read_text(encoding='utf-8', errors='ignore')
308
634
 
309
- # Extract memory items
310
- extracted_items = self.extract_from_text(content, str(file_path.relative_to(self.project_root)))
635
+ # Handle different file types
636
+ file_type = doc_config.get('file_type', 'markdown')
637
+
638
+ if file_type == 'config':
639
+ extracted_items = self._extract_from_config_file(content, file_path, doc_config)
640
+ elif file_type == 'source':
641
+ extracted_items = self._extract_from_source_file(content, file_path, doc_config)
642
+ else:
643
+ # Default markdown/text processing
644
+ extracted_items = self.extract_from_text(content, str(file_path.relative_to(self.project_root)))
311
645
 
312
646
  result = {
313
647
  "success": True,
@@ -23,6 +23,7 @@ information than lose important insights.
23
23
  """
24
24
 
25
25
  import re
26
+ import os
26
27
  from pathlib import Path
27
28
  from typing import Dict, List, Optional, Any, Set, Tuple
28
29
  from datetime import datetime
@@ -57,16 +58,19 @@ class MemoryOptimizer(LoggerMixin):
57
58
  'low': ['note', 'tip', 'hint', 'example', 'reference']
58
59
  }
59
60
 
60
- def __init__(self, config: Optional[Config] = None):
61
+ def __init__(self, config: Optional[Config] = None, working_directory: Optional[Path] = None):
61
62
  """Initialize the memory optimizer.
62
63
 
63
64
  Args:
64
65
  config: Optional Config object
66
+ working_directory: Optional working directory. If not provided, uses current working directory.
65
67
  """
66
68
  super().__init__()
67
69
  self.config = config or Config()
68
70
  self.project_root = PathResolver.get_project_root()
69
- self.memories_dir = self.project_root / ".claude-mpm" / "memories"
71
+ # Use current working directory by default, not project root
72
+ self.working_directory = working_directory or Path(os.getcwd())
73
+ self.memories_dir = self.working_directory / ".claude-mpm" / "memories"
70
74
 
71
75
  def optimize_agent_memory(self, agent_id: str) -> Dict[str, Any]:
72
76
  """Optimize memory for a specific agent.