claude-mpm 4.3.19__py3-none-any.whl → 4.3.22__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 (76) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/agent_loader.py +2 -2
  3. claude_mpm/agents/agent_loader_integration.py +2 -2
  4. claude_mpm/agents/async_agent_loader.py +2 -2
  5. claude_mpm/agents/base_agent_loader.py +2 -2
  6. claude_mpm/agents/frontmatter_validator.py +2 -2
  7. claude_mpm/agents/system_agent_config.py +2 -2
  8. claude_mpm/agents/templates/clerk-ops.json +6 -4
  9. claude_mpm/agents/templates/data_engineer.json +1 -2
  10. claude_mpm/cli/commands/doctor.py +2 -2
  11. claude_mpm/cli/commands/mpm_init.py +560 -47
  12. claude_mpm/cli/commands/mpm_init_handler.py +6 -0
  13. claude_mpm/cli/parsers/mpm_init_parser.py +39 -1
  14. claude_mpm/cli/startup_logging.py +11 -9
  15. claude_mpm/commands/mpm-init.md +76 -12
  16. claude_mpm/config/agent_config.py +2 -2
  17. claude_mpm/config/paths.py +2 -2
  18. claude_mpm/core/agent_name_normalizer.py +2 -2
  19. claude_mpm/core/config.py +2 -1
  20. claude_mpm/core/config_aliases.py +2 -2
  21. claude_mpm/core/file_utils.py +1 -0
  22. claude_mpm/core/log_manager.py +2 -2
  23. claude_mpm/core/tool_access_control.py +2 -2
  24. claude_mpm/core/unified_agent_registry.py +2 -2
  25. claude_mpm/core/unified_paths.py +2 -2
  26. claude_mpm/experimental/cli_enhancements.py +3 -2
  27. claude_mpm/hooks/base_hook.py +2 -2
  28. claude_mpm/hooks/instruction_reinforcement.py +2 -2
  29. claude_mpm/hooks/validation_hooks.py +2 -2
  30. claude_mpm/scripts/mpm_doctor.py +2 -2
  31. claude_mpm/services/agents/loading/agent_profile_loader.py +2 -2
  32. claude_mpm/services/agents/loading/base_agent_manager.py +2 -2
  33. claude_mpm/services/agents/loading/framework_agent_loader.py +2 -2
  34. claude_mpm/services/agents/management/agent_capabilities_generator.py +2 -2
  35. claude_mpm/services/agents/management/agent_management_service.py +2 -2
  36. claude_mpm/services/agents/memory/memory_categorization_service.py +5 -2
  37. claude_mpm/services/agents/memory/memory_file_service.py +27 -6
  38. claude_mpm/services/agents/memory/memory_format_service.py +5 -2
  39. claude_mpm/services/agents/memory/memory_limits_service.py +3 -2
  40. claude_mpm/services/agents/registry/deployed_agent_discovery.py +2 -2
  41. claude_mpm/services/agents/registry/modification_tracker.py +4 -4
  42. claude_mpm/services/async_session_logger.py +2 -1
  43. claude_mpm/services/claude_session_logger.py +2 -2
  44. claude_mpm/services/core/path_resolver.py +3 -2
  45. claude_mpm/services/diagnostics/diagnostic_runner.py +4 -3
  46. claude_mpm/services/event_bus/direct_relay.py +2 -1
  47. claude_mpm/services/event_bus/event_bus.py +2 -1
  48. claude_mpm/services/event_bus/relay.py +2 -2
  49. claude_mpm/services/framework_claude_md_generator/content_assembler.py +2 -2
  50. claude_mpm/services/infrastructure/daemon_manager.py +2 -2
  51. claude_mpm/services/memory/cache/simple_cache.py +2 -2
  52. claude_mpm/services/project/archive_manager.py +981 -0
  53. claude_mpm/services/project/documentation_manager.py +536 -0
  54. claude_mpm/services/project/enhanced_analyzer.py +491 -0
  55. claude_mpm/services/project/project_organizer.py +904 -0
  56. claude_mpm/services/response_tracker.py +2 -2
  57. claude_mpm/services/socketio/handlers/connection.py +14 -33
  58. claude_mpm/services/socketio/server/eventbus_integration.py +2 -2
  59. claude_mpm/services/version_control/version_parser.py +5 -4
  60. claude_mpm/storage/state_storage.py +2 -2
  61. claude_mpm/utils/agent_dependency_loader.py +49 -0
  62. claude_mpm/utils/common.py +542 -0
  63. claude_mpm/utils/database_connector.py +298 -0
  64. claude_mpm/utils/error_handler.py +2 -1
  65. claude_mpm/utils/log_cleanup.py +2 -2
  66. claude_mpm/utils/path_operations.py +2 -2
  67. claude_mpm/utils/robust_installer.py +56 -0
  68. claude_mpm/utils/session_logging.py +2 -2
  69. claude_mpm/utils/subprocess_utils.py +2 -2
  70. claude_mpm/validation/agent_validator.py +2 -2
  71. {claude_mpm-4.3.19.dist-info → claude_mpm-4.3.22.dist-info}/METADATA +1 -1
  72. {claude_mpm-4.3.19.dist-info → claude_mpm-4.3.22.dist-info}/RECORD +76 -70
  73. {claude_mpm-4.3.19.dist-info → claude_mpm-4.3.22.dist-info}/WHEEL +0 -0
  74. {claude_mpm-4.3.19.dist-info → claude_mpm-4.3.22.dist-info}/entry_points.txt +0 -0
  75. {claude_mpm-4.3.19.dist-info → claude_mpm-4.3.22.dist-info}/licenses/LICENSE +0 -0
  76. {claude_mpm-4.3.19.dist-info → claude_mpm-4.3.22.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,542 @@
1
+ """
2
+ Common utility functions to replace duplicate implementations across the codebase.
3
+
4
+ This module consolidates frequently duplicated utility functions found across
5
+ 50+ files in the claude-mpm codebase.
6
+ """
7
+
8
+ import json
9
+ import os
10
+ import subprocess
11
+ import sys
12
+ from pathlib import Path
13
+ from typing import Any, Dict, List, Optional, Union
14
+
15
+ import yaml
16
+
17
+ # Import our centralized logger
18
+ from claude_mpm.core.logging_utils import get_logger
19
+
20
+ logger = get_logger(__name__)
21
+
22
+
23
+ # ==============================================================================
24
+ # JSON/YAML UTILITIES
25
+ # ==============================================================================
26
+
27
+
28
+ def load_json_safe(
29
+ file_path: Union[str, Path],
30
+ default: Optional[Any] = None,
31
+ encoding: str = "utf-8",
32
+ ) -> Any:
33
+ """
34
+ Safely load JSON from a file with error handling.
35
+
36
+ Replaces 20+ duplicate implementations across the codebase.
37
+
38
+ Args:
39
+ file_path: Path to JSON file
40
+ default: Default value if file doesn't exist or is invalid
41
+ encoding: File encoding
42
+
43
+ Returns:
44
+ Parsed JSON data or default value
45
+ """
46
+ file_path = Path(file_path)
47
+
48
+ try:
49
+ with open(file_path, "r", encoding=encoding) as f:
50
+ return json.load(f)
51
+ except FileNotFoundError:
52
+ logger.debug(f"JSON file not found: {file_path}")
53
+ return default if default is not None else {}
54
+ except json.JSONDecodeError as e:
55
+ logger.warning(f"Invalid JSON in {file_path}: {e}")
56
+ return default if default is not None else {}
57
+ except Exception as e:
58
+ logger.error(f"Error loading JSON from {file_path}: {e}")
59
+ return default if default is not None else {}
60
+
61
+
62
+ def save_json_safe(
63
+ file_path: Union[str, Path],
64
+ data: Any,
65
+ indent: int = 2,
66
+ encoding: str = "utf-8",
67
+ create_parents: bool = True,
68
+ ) -> bool:
69
+ """
70
+ Safely save data to JSON file with error handling.
71
+
72
+ Args:
73
+ file_path: Path to save JSON file
74
+ data: Data to serialize
75
+ indent: JSON indentation level
76
+ encoding: File encoding
77
+ create_parents: Create parent directories if they don't exist
78
+
79
+ Returns:
80
+ True if successful, False otherwise
81
+ """
82
+ file_path = Path(file_path)
83
+
84
+ try:
85
+ if create_parents:
86
+ file_path.parent.mkdir(parents=True, exist_ok=True)
87
+
88
+ with open(file_path, "w", encoding=encoding) as f:
89
+ json.dump(data, f, indent=indent, ensure_ascii=False)
90
+ return True
91
+ except Exception as e:
92
+ logger.error(f"Error saving JSON to {file_path}: {e}")
93
+ return False
94
+
95
+
96
+ def load_yaml_safe(
97
+ file_path: Union[str, Path],
98
+ default: Optional[Any] = None,
99
+ encoding: str = "utf-8",
100
+ ) -> Any:
101
+ """
102
+ Safely load YAML from a file with error handling.
103
+
104
+ Args:
105
+ file_path: Path to YAML file
106
+ default: Default value if file doesn't exist or is invalid
107
+ encoding: File encoding
108
+
109
+ Returns:
110
+ Parsed YAML data or default value
111
+ """
112
+ file_path = Path(file_path)
113
+
114
+ try:
115
+ with open(file_path, "r", encoding=encoding) as f:
116
+ return yaml.safe_load(f) or default or {}
117
+ except FileNotFoundError:
118
+ logger.debug(f"YAML file not found: {file_path}")
119
+ return default if default is not None else {}
120
+ except yaml.YAMLError as e:
121
+ logger.warning(f"Invalid YAML in {file_path}: {e}")
122
+ return default if default is not None else {}
123
+ except Exception as e:
124
+ logger.error(f"Error loading YAML from {file_path}: {e}")
125
+ return default if default is not None else {}
126
+
127
+
128
+ def save_yaml_safe(
129
+ file_path: Union[str, Path],
130
+ data: Any,
131
+ encoding: str = "utf-8",
132
+ create_parents: bool = True,
133
+ ) -> bool:
134
+ """
135
+ Safely save data to YAML file with error handling.
136
+
137
+ Args:
138
+ file_path: Path to save YAML file
139
+ data: Data to serialize
140
+ encoding: File encoding
141
+ create_parents: Create parent directories if they don't exist
142
+
143
+ Returns:
144
+ True if successful, False otherwise
145
+ """
146
+ file_path = Path(file_path)
147
+
148
+ try:
149
+ if create_parents:
150
+ file_path.parent.mkdir(parents=True, exist_ok=True)
151
+
152
+ with open(file_path, "w", encoding=encoding) as f:
153
+ yaml.safe_dump(data, f, default_flow_style=False, allow_unicode=True)
154
+ return True
155
+ except Exception as e:
156
+ logger.error(f"Error saving YAML to {file_path}: {e}")
157
+ return False
158
+
159
+
160
+ # ==============================================================================
161
+ # PATH/FILE UTILITIES
162
+ # ==============================================================================
163
+
164
+
165
+ def ensure_path_exists(
166
+ path: Union[str, Path],
167
+ create_parents: bool = True,
168
+ is_file: bool = False,
169
+ ) -> bool:
170
+ """
171
+ Ensure a path exists, optionally creating parent directories.
172
+
173
+ Replaces 50+ duplicate path existence checks.
174
+
175
+ Args:
176
+ path: Path to check/create
177
+ create_parents: Create parent directories if needed
178
+ is_file: If True, path is a file (create parent dir only)
179
+
180
+ Returns:
181
+ True if path exists or was created, False otherwise
182
+ """
183
+ path = Path(path)
184
+
185
+ try:
186
+ if path.exists():
187
+ return True
188
+
189
+ if is_file:
190
+ if create_parents:
191
+ path.parent.mkdir(parents=True, exist_ok=True)
192
+ # Don't create the file itself, just ensure parent exists
193
+ return path.parent.exists()
194
+ else:
195
+ if create_parents:
196
+ path.mkdir(parents=True, exist_ok=True)
197
+ return True
198
+ return False
199
+ except Exception as e:
200
+ logger.error(f"Error ensuring path exists {path}: {e}")
201
+ return False
202
+
203
+
204
+ def read_file_if_exists(
205
+ file_path: Union[str, Path],
206
+ encoding: str = "utf-8",
207
+ default: str = "",
208
+ ) -> Optional[str]:
209
+ """
210
+ Read file contents if it exists, otherwise return default.
211
+
212
+ Args:
213
+ file_path: Path to file
214
+ encoding: File encoding
215
+ default: Default value if file doesn't exist
216
+
217
+ Returns:
218
+ File contents or default value
219
+ """
220
+ file_path = Path(file_path)
221
+
222
+ try:
223
+ if file_path.exists() and file_path.is_file():
224
+ return file_path.read_text(encoding=encoding)
225
+ return default
226
+ except Exception as e:
227
+ logger.error(f"Error reading file {file_path}: {e}")
228
+ return default
229
+
230
+
231
+ def write_file_safe(
232
+ file_path: Union[str, Path],
233
+ content: str,
234
+ encoding: str = "utf-8",
235
+ create_parents: bool = True,
236
+ ) -> bool:
237
+ """
238
+ Safely write content to file with error handling.
239
+
240
+ Args:
241
+ file_path: Path to file
242
+ content: Content to write
243
+ encoding: File encoding
244
+ create_parents: Create parent directories if needed
245
+
246
+ Returns:
247
+ True if successful, False otherwise
248
+ """
249
+ file_path = Path(file_path)
250
+
251
+ try:
252
+ if create_parents:
253
+ file_path.parent.mkdir(parents=True, exist_ok=True)
254
+
255
+ file_path.write_text(content, encoding=encoding)
256
+ return True
257
+ except Exception as e:
258
+ logger.error(f"Error writing file {file_path}: {e}")
259
+ return False
260
+
261
+
262
+ def get_file_size(file_path: Union[str, Path]) -> int:
263
+ """
264
+ Get file size in bytes, returning 0 if file doesn't exist.
265
+
266
+ Args:
267
+ file_path: Path to file
268
+
269
+ Returns:
270
+ File size in bytes or 0
271
+ """
272
+ file_path = Path(file_path)
273
+
274
+ try:
275
+ if file_path.exists() and file_path.is_file():
276
+ return file_path.stat().st_size
277
+ return 0
278
+ except Exception as e:
279
+ logger.error(f"Error getting file size {file_path}: {e}")
280
+ return 0
281
+
282
+
283
+ def find_files(
284
+ directory: Union[str, Path],
285
+ pattern: str = "*",
286
+ recursive: bool = True,
287
+ ) -> List[Path]:
288
+ """
289
+ Find files matching a pattern in a directory.
290
+
291
+ Args:
292
+ directory: Directory to search
293
+ pattern: Glob pattern
294
+ recursive: Search recursively
295
+
296
+ Returns:
297
+ List of matching file paths
298
+ """
299
+ directory = Path(directory)
300
+
301
+ try:
302
+ if not directory.exists():
303
+ return []
304
+
305
+ if recursive:
306
+ return list(directory.rglob(pattern))
307
+ else:
308
+ return list(directory.glob(pattern))
309
+ except Exception as e:
310
+ logger.error(f"Error finding files in {directory}: {e}")
311
+ return []
312
+
313
+
314
+ # ==============================================================================
315
+ # SUBPROCESS UTILITIES
316
+ # ==============================================================================
317
+
318
+
319
+ def run_command_safe(
320
+ command: Union[str, List[str]],
321
+ cwd: Optional[Union[str, Path]] = None,
322
+ capture_output: bool = True,
323
+ check: bool = False,
324
+ timeout: Optional[int] = None,
325
+ env: Optional[Dict[str, str]] = None,
326
+ ) -> subprocess.CompletedProcess:
327
+ """
328
+ Safely run a subprocess command with error handling.
329
+
330
+ Replaces 15+ duplicate subprocess patterns.
331
+
332
+ Args:
333
+ command: Command to run (string or list)
334
+ cwd: Working directory
335
+ capture_output: Capture stdout/stderr
336
+ check: Raise exception on non-zero return code
337
+ timeout: Command timeout in seconds
338
+ env: Environment variables
339
+
340
+ Returns:
341
+ CompletedProcess result
342
+ """
343
+ try:
344
+ if isinstance(command, str):
345
+ shell = True
346
+ else:
347
+ shell = False
348
+
349
+ result = subprocess.run(
350
+ command,
351
+ shell=shell,
352
+ cwd=cwd,
353
+ capture_output=capture_output,
354
+ text=True,
355
+ check=check,
356
+ timeout=timeout,
357
+ env=env,
358
+ )
359
+ return result
360
+ except subprocess.TimeoutExpired as e:
361
+ logger.error(f"Command timed out: {command}")
362
+ raise
363
+ except subprocess.CalledProcessError as e:
364
+ logger.error(f"Command failed: {command}, return code: {e.returncode}")
365
+ raise
366
+ except Exception as e:
367
+ logger.error(f"Error running command {command}: {e}")
368
+ raise
369
+
370
+
371
+ def check_command_exists(command: str) -> bool:
372
+ """
373
+ Check if a command exists in the system PATH.
374
+
375
+ Args:
376
+ command: Command name to check
377
+
378
+ Returns:
379
+ True if command exists, False otherwise
380
+ """
381
+ try:
382
+ result = run_command_safe(
383
+ ["which", command] if sys.platform != "win32" else ["where", command],
384
+ capture_output=True,
385
+ check=False,
386
+ )
387
+ return result.returncode == 0
388
+ except Exception:
389
+ return False
390
+
391
+
392
+ # ==============================================================================
393
+ # ENVIRONMENT UTILITIES
394
+ # ==============================================================================
395
+
396
+
397
+ def get_env_bool(key: str, default: bool = False) -> bool:
398
+ """
399
+ Get boolean value from environment variable.
400
+
401
+ Args:
402
+ key: Environment variable key
403
+ default: Default value if not set
404
+
405
+ Returns:
406
+ Boolean value
407
+ """
408
+ value = os.environ.get(key, "").lower()
409
+
410
+ if not value:
411
+ return default
412
+
413
+ return value in ("1", "true", "yes", "on")
414
+
415
+
416
+ def get_env_int(key: str, default: int = 0) -> int:
417
+ """
418
+ Get integer value from environment variable.
419
+
420
+ Args:
421
+ key: Environment variable key
422
+ default: Default value if not set or invalid
423
+
424
+ Returns:
425
+ Integer value
426
+ """
427
+ value = os.environ.get(key, "")
428
+
429
+ if not value:
430
+ return default
431
+
432
+ try:
433
+ return int(value)
434
+ except ValueError:
435
+ logger.warning(f"Invalid integer value for {key}: {value}")
436
+ return default
437
+
438
+
439
+ def get_env_list(key: str, separator: str = ",", default: Optional[List[str]] = None) -> List[str]:
440
+ """
441
+ Get list from environment variable.
442
+
443
+ Args:
444
+ key: Environment variable key
445
+ separator: List separator
446
+ default: Default list if not set
447
+
448
+ Returns:
449
+ List of values
450
+ """
451
+ value = os.environ.get(key, "")
452
+
453
+ if not value:
454
+ return default or []
455
+
456
+ return [item.strip() for item in value.split(separator) if item.strip()]
457
+
458
+
459
+ # ==============================================================================
460
+ # IMPORT UTILITIES
461
+ # ==============================================================================
462
+
463
+
464
+ def safe_import(module_name: str, fallback: Any = None) -> Any:
465
+ """
466
+ Safely import a module with fallback.
467
+
468
+ Replaces 40+ duplicate import error handling patterns.
469
+
470
+ Args:
471
+ module_name: Module to import
472
+ fallback: Fallback value if import fails
473
+
474
+ Returns:
475
+ Imported module or fallback
476
+ """
477
+ try:
478
+ import importlib
479
+ return importlib.import_module(module_name)
480
+ except ImportError as e:
481
+ logger.debug(f"Could not import {module_name}: {e}")
482
+ return fallback
483
+ except Exception as e:
484
+ logger.error(f"Error importing {module_name}: {e}")
485
+ return fallback
486
+
487
+
488
+ def import_from_string(import_path: str, fallback: Any = None) -> Any:
489
+ """
490
+ Import a class or function from a string path.
491
+
492
+ Args:
493
+ import_path: Full import path (e.g., "package.module.ClassName")
494
+ fallback: Fallback value if import fails
495
+
496
+ Returns:
497
+ Imported object or fallback
498
+ """
499
+ try:
500
+ module_path, attr_name = import_path.rsplit(".", 1)
501
+ module = safe_import(module_path)
502
+
503
+ if module is None:
504
+ return fallback
505
+
506
+ return getattr(module, attr_name, fallback)
507
+ except (ValueError, AttributeError) as e:
508
+ logger.debug(f"Could not import {import_path}: {e}")
509
+ return fallback
510
+
511
+
512
+ # ==============================================================================
513
+ # DEPRECATION WARNINGS
514
+ # ==============================================================================
515
+
516
+
517
+ def deprecated(replacement: str = None):
518
+ """
519
+ Decorator to mark functions as deprecated.
520
+
521
+ Args:
522
+ replacement: Suggested replacement function
523
+
524
+ Returns:
525
+ Decorated function
526
+ """
527
+ def decorator(func):
528
+ def wrapper(*args, **kwargs):
529
+ import warnings
530
+
531
+ message = f"{func.__name__} is deprecated"
532
+ if replacement:
533
+ message += f", use {replacement} instead"
534
+
535
+ warnings.warn(message, DeprecationWarning, stacklevel=2)
536
+ return func(*args, **kwargs)
537
+
538
+ wrapper.__name__ = func.__name__
539
+ wrapper.__doc__ = func.__doc__
540
+ return wrapper
541
+
542
+ return decorator