claude-mpm 0.3.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.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (159) hide show
  1. claude_mpm/__init__.py +17 -0
  2. claude_mpm/__main__.py +14 -0
  3. claude_mpm/_version.py +32 -0
  4. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +88 -0
  5. claude_mpm/agents/INSTRUCTIONS.md +375 -0
  6. claude_mpm/agents/__init__.py +118 -0
  7. claude_mpm/agents/agent_loader.py +621 -0
  8. claude_mpm/agents/agent_loader_integration.py +229 -0
  9. claude_mpm/agents/agents_metadata.py +204 -0
  10. claude_mpm/agents/base_agent.json +27 -0
  11. claude_mpm/agents/base_agent_loader.py +519 -0
  12. claude_mpm/agents/schema/agent_schema.json +160 -0
  13. claude_mpm/agents/system_agent_config.py +587 -0
  14. claude_mpm/agents/templates/__init__.py +101 -0
  15. claude_mpm/agents/templates/data_engineer_agent.json +46 -0
  16. claude_mpm/agents/templates/documentation_agent.json +45 -0
  17. claude_mpm/agents/templates/engineer_agent.json +49 -0
  18. claude_mpm/agents/templates/ops_agent.json +46 -0
  19. claude_mpm/agents/templates/qa_agent.json +45 -0
  20. claude_mpm/agents/templates/research_agent.json +49 -0
  21. claude_mpm/agents/templates/security_agent.json +46 -0
  22. claude_mpm/agents/templates/update-optimized-specialized-agents.json +374 -0
  23. claude_mpm/agents/templates/version_control_agent.json +46 -0
  24. claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +6 -0
  25. claude_mpm/cli.py +655 -0
  26. claude_mpm/cli_main.py +13 -0
  27. claude_mpm/cli_module/__init__.py +15 -0
  28. claude_mpm/cli_module/args.py +222 -0
  29. claude_mpm/cli_module/commands.py +203 -0
  30. claude_mpm/cli_module/migration_example.py +183 -0
  31. claude_mpm/cli_module/refactoring_guide.md +253 -0
  32. claude_mpm/cli_old/__init__.py +1 -0
  33. claude_mpm/cli_old/ticket_cli.py +102 -0
  34. claude_mpm/config/__init__.py +5 -0
  35. claude_mpm/config/hook_config.py +42 -0
  36. claude_mpm/constants.py +150 -0
  37. claude_mpm/core/__init__.py +45 -0
  38. claude_mpm/core/agent_name_normalizer.py +248 -0
  39. claude_mpm/core/agent_registry.py +627 -0
  40. claude_mpm/core/agent_registry.py.bak +312 -0
  41. claude_mpm/core/agent_session_manager.py +273 -0
  42. claude_mpm/core/base_service.py +747 -0
  43. claude_mpm/core/base_service.py.bak +406 -0
  44. claude_mpm/core/config.py +334 -0
  45. claude_mpm/core/config_aliases.py +292 -0
  46. claude_mpm/core/container.py +347 -0
  47. claude_mpm/core/factories.py +281 -0
  48. claude_mpm/core/framework_loader.py +472 -0
  49. claude_mpm/core/injectable_service.py +206 -0
  50. claude_mpm/core/interfaces.py +539 -0
  51. claude_mpm/core/logger.py +468 -0
  52. claude_mpm/core/minimal_framework_loader.py +107 -0
  53. claude_mpm/core/mixins.py +150 -0
  54. claude_mpm/core/service_registry.py +299 -0
  55. claude_mpm/core/session_manager.py +190 -0
  56. claude_mpm/core/simple_runner.py +511 -0
  57. claude_mpm/core/tool_access_control.py +173 -0
  58. claude_mpm/hooks/README.md +243 -0
  59. claude_mpm/hooks/__init__.py +5 -0
  60. claude_mpm/hooks/base_hook.py +154 -0
  61. claude_mpm/hooks/builtin/__init__.py +1 -0
  62. claude_mpm/hooks/builtin/logging_hook_example.py +165 -0
  63. claude_mpm/hooks/builtin/post_delegation_hook_example.py +124 -0
  64. claude_mpm/hooks/builtin/pre_delegation_hook_example.py +125 -0
  65. claude_mpm/hooks/builtin/submit_hook_example.py +100 -0
  66. claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +237 -0
  67. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +239 -0
  68. claude_mpm/hooks/builtin/workflow_start_hook.py +181 -0
  69. claude_mpm/hooks/hook_client.py +264 -0
  70. claude_mpm/hooks/hook_runner.py +370 -0
  71. claude_mpm/hooks/json_rpc_executor.py +259 -0
  72. claude_mpm/hooks/json_rpc_hook_client.py +319 -0
  73. claude_mpm/hooks/tool_call_interceptor.py +204 -0
  74. claude_mpm/init.py +246 -0
  75. claude_mpm/orchestration/SUBPROCESS_DESIGN.md +66 -0
  76. claude_mpm/orchestration/__init__.py +6 -0
  77. claude_mpm/orchestration/archive/direct_orchestrator.py +195 -0
  78. claude_mpm/orchestration/archive/factory.py +215 -0
  79. claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +188 -0
  80. claude_mpm/orchestration/archive/hook_integration_example.py +178 -0
  81. claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +826 -0
  82. claude_mpm/orchestration/archive/orchestrator.py +501 -0
  83. claude_mpm/orchestration/archive/pexpect_orchestrator.py +252 -0
  84. claude_mpm/orchestration/archive/pty_orchestrator.py +270 -0
  85. claude_mpm/orchestration/archive/simple_orchestrator.py +82 -0
  86. claude_mpm/orchestration/archive/subprocess_orchestrator.py +801 -0
  87. claude_mpm/orchestration/archive/system_prompt_orchestrator.py +278 -0
  88. claude_mpm/orchestration/archive/wrapper_orchestrator.py +187 -0
  89. claude_mpm/scripts/__init__.py +1 -0
  90. claude_mpm/scripts/ticket.py +269 -0
  91. claude_mpm/services/__init__.py +10 -0
  92. claude_mpm/services/agent_deployment.py +955 -0
  93. claude_mpm/services/agent_lifecycle_manager.py +948 -0
  94. claude_mpm/services/agent_management_service.py +596 -0
  95. claude_mpm/services/agent_modification_tracker.py +841 -0
  96. claude_mpm/services/agent_profile_loader.py +606 -0
  97. claude_mpm/services/agent_registry.py +677 -0
  98. claude_mpm/services/base_agent_manager.py +380 -0
  99. claude_mpm/services/framework_agent_loader.py +337 -0
  100. claude_mpm/services/framework_claude_md_generator/README.md +92 -0
  101. claude_mpm/services/framework_claude_md_generator/__init__.py +206 -0
  102. claude_mpm/services/framework_claude_md_generator/content_assembler.py +151 -0
  103. claude_mpm/services/framework_claude_md_generator/content_validator.py +126 -0
  104. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +137 -0
  105. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +106 -0
  106. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +582 -0
  107. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +97 -0
  108. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +27 -0
  109. claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +23 -0
  110. claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +23 -0
  111. claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +20 -0
  112. claude_mpm/services/framework_claude_md_generator/section_generators/header.py +26 -0
  113. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +30 -0
  114. claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +37 -0
  115. claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +111 -0
  116. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +89 -0
  117. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +39 -0
  118. claude_mpm/services/framework_claude_md_generator/section_manager.py +106 -0
  119. claude_mpm/services/framework_claude_md_generator/version_manager.py +121 -0
  120. claude_mpm/services/framework_claude_md_generator.py +621 -0
  121. claude_mpm/services/hook_service.py +388 -0
  122. claude_mpm/services/hook_service_manager.py +223 -0
  123. claude_mpm/services/json_rpc_hook_manager.py +92 -0
  124. claude_mpm/services/parent_directory_manager/README.md +83 -0
  125. claude_mpm/services/parent_directory_manager/__init__.py +577 -0
  126. claude_mpm/services/parent_directory_manager/backup_manager.py +258 -0
  127. claude_mpm/services/parent_directory_manager/config_manager.py +210 -0
  128. claude_mpm/services/parent_directory_manager/deduplication_manager.py +279 -0
  129. claude_mpm/services/parent_directory_manager/framework_protector.py +143 -0
  130. claude_mpm/services/parent_directory_manager/operations.py +186 -0
  131. claude_mpm/services/parent_directory_manager/state_manager.py +624 -0
  132. claude_mpm/services/parent_directory_manager/template_deployer.py +579 -0
  133. claude_mpm/services/parent_directory_manager/validation_manager.py +378 -0
  134. claude_mpm/services/parent_directory_manager/version_control_helper.py +339 -0
  135. claude_mpm/services/parent_directory_manager/version_manager.py +222 -0
  136. claude_mpm/services/shared_prompt_cache.py +819 -0
  137. claude_mpm/services/ticket_manager.py +213 -0
  138. claude_mpm/services/ticket_manager_di.py +318 -0
  139. claude_mpm/services/ticketing_service_original.py +508 -0
  140. claude_mpm/services/version_control/VERSION +1 -0
  141. claude_mpm/services/version_control/__init__.py +70 -0
  142. claude_mpm/services/version_control/branch_strategy.py +670 -0
  143. claude_mpm/services/version_control/conflict_resolution.py +744 -0
  144. claude_mpm/services/version_control/git_operations.py +784 -0
  145. claude_mpm/services/version_control/semantic_versioning.py +703 -0
  146. claude_mpm/ui/__init__.py +1 -0
  147. claude_mpm/ui/rich_terminal_ui.py +295 -0
  148. claude_mpm/ui/terminal_ui.py +328 -0
  149. claude_mpm/utils/__init__.py +16 -0
  150. claude_mpm/utils/config_manager.py +468 -0
  151. claude_mpm/utils/import_migration_example.py +80 -0
  152. claude_mpm/utils/imports.py +182 -0
  153. claude_mpm/utils/path_operations.py +357 -0
  154. claude_mpm/utils/paths.py +289 -0
  155. claude_mpm-0.3.0.dist-info/METADATA +290 -0
  156. claude_mpm-0.3.0.dist-info/RECORD +159 -0
  157. claude_mpm-0.3.0.dist-info/WHEEL +5 -0
  158. claude_mpm-0.3.0.dist-info/entry_points.txt +4 -0
  159. claude_mpm-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,182 @@
1
+ """Import utilities for handling relative and absolute imports.
2
+
3
+ This module provides utilities to handle the common pattern of trying
4
+ relative imports first and falling back to absolute imports, which is
5
+ used throughout the claude_mpm codebase.
6
+ """
7
+
8
+ import importlib
9
+ import logging
10
+ from typing import Optional, Any, List, Union
11
+
12
+
13
+ def safe_import(
14
+ module_name: str,
15
+ fallback_name: Optional[str] = None,
16
+ from_list: Optional[List[str]] = None,
17
+ logger: Optional[logging.Logger] = None
18
+ ) -> Optional[Any]:
19
+ """
20
+ Safely import a module with fallback support.
21
+
22
+ Attempts to import a module using the primary module name first,
23
+ then falls back to the fallback name if provided. This is useful
24
+ for handling both relative and absolute imports.
25
+
26
+ Args:
27
+ module_name: Primary module name to import (e.g., '..utils.logger')
28
+ fallback_name: Fallback module name if primary fails (e.g., 'utils.logger')
29
+ from_list: List of names to import from the module (for 'from X import Y' style)
30
+ logger: Optional logger for debugging import attempts
31
+
32
+ Returns:
33
+ The imported module or specific attribute(s) if from_list is provided.
34
+ Returns None if all import attempts fail.
35
+
36
+ Examples:
37
+ # Import entire module
38
+ logger_module = safe_import('..utils.logger', 'utils.logger')
39
+
40
+ # Import specific function (returns the function directly)
41
+ get_logger = safe_import('..utils.logger', 'utils.logger', ['get_logger'])
42
+
43
+ # Import multiple items (returns tuple)
44
+ logger, setup = safe_import('..utils.logger', 'utils.logger',
45
+ ['get_logger', 'setup_logging'])
46
+ """
47
+ # Try primary import
48
+ try:
49
+ if logger:
50
+ logger.debug(f"Attempting import: {module_name}")
51
+
52
+ module = importlib.import_module(module_name)
53
+
54
+ if from_list:
55
+ # Handle 'from X import Y' style imports
56
+ results = []
57
+ for name in from_list:
58
+ if hasattr(module, name):
59
+ results.append(getattr(module, name))
60
+ else:
61
+ if logger:
62
+ logger.warning(f"Module {module_name} has no attribute {name}")
63
+ results.append(None)
64
+
65
+ # Return single item if only one requested, otherwise tuple
66
+ if len(results) == 1:
67
+ return results[0]
68
+ return tuple(results)
69
+
70
+ return module
71
+
72
+ except ImportError as e:
73
+ if logger:
74
+ logger.debug(f"Primary import failed: {e}")
75
+
76
+ # Try fallback if provided
77
+ if fallback_name:
78
+ try:
79
+ if logger:
80
+ logger.debug(f"Attempting fallback import: {fallback_name}")
81
+
82
+ module = importlib.import_module(fallback_name)
83
+
84
+ if from_list:
85
+ # Handle 'from X import Y' style imports
86
+ results = []
87
+ for name in from_list:
88
+ if hasattr(module, name):
89
+ results.append(getattr(module, name))
90
+ else:
91
+ if logger:
92
+ logger.warning(f"Module {fallback_name} has no attribute {name}")
93
+ results.append(None)
94
+
95
+ # Return single item if only one requested, otherwise tuple
96
+ if len(results) == 1:
97
+ return results[0]
98
+ return tuple(results)
99
+
100
+ return module
101
+
102
+ except ImportError as e2:
103
+ if logger:
104
+ logger.debug(f"Fallback import also failed: {e2}")
105
+
106
+ # All imports failed
107
+ if logger:
108
+ logger.error(f"Failed to import {module_name}" +
109
+ (f" or {fallback_name}" if fallback_name else ""))
110
+
111
+ return None
112
+
113
+
114
+ def safe_import_multiple(
115
+ imports: List[Union[tuple, dict]],
116
+ logger: Optional[logging.Logger] = None
117
+ ) -> dict:
118
+ """
119
+ Import multiple modules with fallback support.
120
+
121
+ Args:
122
+ imports: List of import specifications. Each can be:
123
+ - tuple: (primary_name, fallback_name, from_list)
124
+ - dict: {'primary': '...', 'fallback': '...', 'from_list': [...], 'as': 'alias'}
125
+ logger: Optional logger for debugging
126
+
127
+ Returns:
128
+ Dictionary mapping module/attribute names to imported objects
129
+
130
+ Example:
131
+ imports = [
132
+ ('..utils.logger', 'utils.logger', ['get_logger']),
133
+ {'primary': '..core.agent_registry', 'fallback': 'core.agent_registry',
134
+ 'from_list': ['AgentRegistry'], 'as': 'registry'}
135
+ ]
136
+
137
+ imported = safe_import_multiple(imports)
138
+ # Result: {'get_logger': <function>, 'registry': <class>}
139
+ """
140
+ results = {}
141
+
142
+ for spec in imports:
143
+ if isinstance(spec, tuple):
144
+ primary, fallback, from_list = spec
145
+ imported = safe_import(primary, fallback, from_list, logger)
146
+
147
+ if imported is not None:
148
+ if from_list and len(from_list) == 1:
149
+ # Single import gets stored by its name
150
+ results[from_list[0]] = imported
151
+ elif from_list and len(from_list) > 1:
152
+ # Multiple imports get unpacked
153
+ for i, name in enumerate(from_list):
154
+ if imported[i] is not None:
155
+ results[name] = imported[i]
156
+ else:
157
+ # Module import gets stored by last part of module name
158
+ module_alias = primary.split('.')[-1]
159
+ results[module_alias] = imported
160
+
161
+ elif isinstance(spec, dict):
162
+ primary = spec.get('primary')
163
+ fallback = spec.get('fallback')
164
+ from_list = spec.get('from_list')
165
+ alias = spec.get('as')
166
+
167
+ imported = safe_import(primary, fallback, from_list, logger)
168
+
169
+ if imported is not None:
170
+ if alias:
171
+ results[alias] = imported
172
+ elif from_list and len(from_list) == 1:
173
+ results[from_list[0]] = imported
174
+ elif from_list and len(from_list) > 1:
175
+ for i, name in enumerate(from_list):
176
+ if imported[i] is not None:
177
+ results[name] = imported[i]
178
+ else:
179
+ module_alias = primary.split('.')[-1]
180
+ results[module_alias] = imported
181
+
182
+ return results
@@ -0,0 +1,357 @@
1
+ """Path operations and validation utilities.
2
+
3
+ This module provides a centralized PathOperations class for common path validation
4
+ and file operations, reducing code duplication across the codebase.
5
+ """
6
+
7
+ import os
8
+ import shutil
9
+ import tempfile
10
+ from pathlib import Path
11
+ from typing import Optional, List, Union, Callable
12
+ import logging
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class PathOperations:
18
+ """Utility class for path validation and safe file operations."""
19
+
20
+ def __init__(self, default_encoding: str = 'utf-8'):
21
+ """Initialize PathOperations with default encoding.
22
+
23
+ Args:
24
+ default_encoding: Default encoding for file operations
25
+ """
26
+ self.default_encoding = default_encoding
27
+
28
+ # Path Validation Methods
29
+
30
+ def validate_exists(self, path: Union[str, Path]) -> bool:
31
+ """Check if path exists.
32
+
33
+ Args:
34
+ path: Path to validate
35
+
36
+ Returns:
37
+ True if path exists, False otherwise
38
+ """
39
+ try:
40
+ return Path(path).exists()
41
+ except Exception as e:
42
+ logger.error(f"Error checking path existence: {e}")
43
+ return False
44
+
45
+ def validate_is_file(self, path: Union[str, Path]) -> bool:
46
+ """Check if path is a file.
47
+
48
+ Args:
49
+ path: Path to validate
50
+
51
+ Returns:
52
+ True if path is a file, False otherwise
53
+ """
54
+ try:
55
+ return Path(path).is_file()
56
+ except Exception as e:
57
+ logger.error(f"Error checking if path is file: {e}")
58
+ return False
59
+
60
+ def validate_is_dir(self, path: Union[str, Path]) -> bool:
61
+ """Check if path is a directory.
62
+
63
+ Args:
64
+ path: Path to validate
65
+
66
+ Returns:
67
+ True if path is a directory, False otherwise
68
+ """
69
+ try:
70
+ return Path(path).is_dir()
71
+ except Exception as e:
72
+ logger.error(f"Error checking if path is directory: {e}")
73
+ return False
74
+
75
+ def validate_readable(self, path: Union[str, Path]) -> bool:
76
+ """Check if path has read permissions.
77
+
78
+ Args:
79
+ path: Path to validate
80
+
81
+ Returns:
82
+ True if path is readable, False otherwise
83
+ """
84
+ try:
85
+ path_obj = Path(path)
86
+ if not path_obj.exists():
87
+ return False
88
+ return os.access(str(path_obj), os.R_OK)
89
+ except Exception as e:
90
+ logger.error(f"Error checking read permissions: {e}")
91
+ return False
92
+
93
+ def validate_writable(self, path: Union[str, Path]) -> bool:
94
+ """Check if path has write permissions.
95
+
96
+ Args:
97
+ path: Path to validate
98
+
99
+ Returns:
100
+ True if path is writable, False otherwise
101
+ """
102
+ try:
103
+ path_obj = Path(path)
104
+ if path_obj.exists():
105
+ return os.access(str(path_obj), os.W_OK)
106
+ # Check parent directory for new files
107
+ parent = path_obj.parent
108
+ return parent.exists() and os.access(str(parent), os.W_OK)
109
+ except Exception as e:
110
+ logger.error(f"Error checking write permissions: {e}")
111
+ return False
112
+
113
+ # Safe File Operations
114
+
115
+ def safe_read(self, path: Union[str, Path],
116
+ encoding: Optional[str] = None,
117
+ default: Optional[str] = None) -> Optional[str]:
118
+ """Read file with error handling.
119
+
120
+ Args:
121
+ path: Path to read
122
+ encoding: File encoding (uses default if None)
123
+ default: Default value if read fails
124
+
125
+ Returns:
126
+ File contents or default value
127
+ """
128
+ encoding = encoding or self.default_encoding
129
+ try:
130
+ path_obj = Path(path)
131
+ if not self.validate_readable(path_obj):
132
+ logger.warning(f"File not readable: {path}")
133
+ return default
134
+
135
+ with open(path_obj, 'r', encoding=encoding) as f:
136
+ return f.read()
137
+ except Exception as e:
138
+ logger.error(f"Error reading file {path}: {e}")
139
+ return default
140
+
141
+ def safe_write(self, path: Union[str, Path],
142
+ content: str,
143
+ encoding: Optional[str] = None,
144
+ backup: bool = False,
145
+ atomic: bool = False) -> bool:
146
+ """Write file with error handling and optional backup.
147
+
148
+ Args:
149
+ path: Path to write
150
+ content: Content to write
151
+ encoding: File encoding (uses default if None)
152
+ backup: Create backup before writing
153
+ atomic: Use atomic write (write to temp file and move)
154
+
155
+ Returns:
156
+ True if write successful, False otherwise
157
+ """
158
+ encoding = encoding or self.default_encoding
159
+ path_obj = Path(path)
160
+
161
+ try:
162
+ # Create backup if requested
163
+ if backup and path_obj.exists():
164
+ backup_path = path_obj.with_suffix(path_obj.suffix + '.bak')
165
+ shutil.copy2(str(path_obj), str(backup_path))
166
+ logger.info(f"Created backup: {backup_path}")
167
+
168
+ # Ensure parent directory exists
169
+ path_obj.parent.mkdir(parents=True, exist_ok=True)
170
+
171
+ if atomic:
172
+ # Atomic write: write to temp file then move
173
+ with tempfile.NamedTemporaryFile(
174
+ mode='w',
175
+ encoding=encoding,
176
+ dir=path_obj.parent,
177
+ delete=False
178
+ ) as tmp_file:
179
+ tmp_file.write(content)
180
+ tmp_path = tmp_file.name
181
+
182
+ # Move temp file to target
183
+ shutil.move(tmp_path, str(path_obj))
184
+ else:
185
+ # Direct write
186
+ with open(path_obj, 'w', encoding=encoding) as f:
187
+ f.write(content)
188
+
189
+ logger.info(f"Successfully wrote to {path}")
190
+ return True
191
+
192
+ except Exception as e:
193
+ logger.error(f"Error writing file {path}: {e}")
194
+ return False
195
+
196
+ def safe_delete(self, path: Union[str, Path],
197
+ confirm: Optional[Callable[[], bool]] = None) -> bool:
198
+ """Delete file/directory with optional confirmation.
199
+
200
+ Args:
201
+ path: Path to delete
202
+ confirm: Optional confirmation callback
203
+
204
+ Returns:
205
+ True if delete successful, False otherwise
206
+ """
207
+ try:
208
+ path_obj = Path(path)
209
+ if not path_obj.exists():
210
+ logger.warning(f"Path does not exist: {path}")
211
+ return True
212
+
213
+ # Confirm if callback provided
214
+ if confirm and not confirm():
215
+ logger.info(f"Delete cancelled by user: {path}")
216
+ return False
217
+
218
+ if path_obj.is_file():
219
+ path_obj.unlink()
220
+ else:
221
+ shutil.rmtree(str(path_obj))
222
+
223
+ logger.info(f"Successfully deleted: {path}")
224
+ return True
225
+
226
+ except Exception as e:
227
+ logger.error(f"Error deleting {path}: {e}")
228
+ return False
229
+
230
+ def safe_copy(self, src: Union[str, Path],
231
+ dst: Union[str, Path],
232
+ overwrite: bool = False) -> bool:
233
+ """Copy file/directory with overwrite protection.
234
+
235
+ Args:
236
+ src: Source path
237
+ dst: Destination path
238
+ overwrite: Allow overwriting existing files
239
+
240
+ Returns:
241
+ True if copy successful, False otherwise
242
+ """
243
+ try:
244
+ src_path = Path(src)
245
+ dst_path = Path(dst)
246
+
247
+ if not src_path.exists():
248
+ logger.error(f"Source does not exist: {src}")
249
+ return False
250
+
251
+ if dst_path.exists() and not overwrite:
252
+ logger.error(f"Destination exists and overwrite=False: {dst}")
253
+ return False
254
+
255
+ # Ensure parent directory exists
256
+ dst_path.parent.mkdir(parents=True, exist_ok=True)
257
+
258
+ if src_path.is_file():
259
+ shutil.copy2(str(src_path), str(dst_path))
260
+ else:
261
+ if dst_path.exists():
262
+ shutil.rmtree(str(dst_path))
263
+ shutil.copytree(str(src_path), str(dst_path))
264
+
265
+ logger.info(f"Successfully copied {src} to {dst}")
266
+ return True
267
+
268
+ except Exception as e:
269
+ logger.error(f"Error copying {src} to {dst}: {e}")
270
+ return False
271
+
272
+ # Common Patterns
273
+
274
+ def ensure_dir(self, path: Union[str, Path]) -> bool:
275
+ """Create directory if it doesn't exist.
276
+
277
+ Args:
278
+ path: Directory path to ensure
279
+
280
+ Returns:
281
+ True if directory exists or was created, False otherwise
282
+ """
283
+ try:
284
+ Path(path).mkdir(parents=True, exist_ok=True)
285
+ return True
286
+ except Exception as e:
287
+ logger.error(f"Error creating directory {path}: {e}")
288
+ return False
289
+
290
+ def get_size(self, path: Union[str, Path]) -> int:
291
+ """Get size of file or directory in bytes.
292
+
293
+ Args:
294
+ path: Path to measure
295
+
296
+ Returns:
297
+ Size in bytes, or -1 on error
298
+ """
299
+ try:
300
+ path_obj = Path(path)
301
+ if not path_obj.exists():
302
+ return -1
303
+
304
+ if path_obj.is_file():
305
+ return path_obj.stat().st_size
306
+ else:
307
+ # Calculate total size for directory
308
+ total = 0
309
+ for item in path_obj.rglob('*'):
310
+ if item.is_file():
311
+ total += item.stat().st_size
312
+ return total
313
+
314
+ except Exception as e:
315
+ logger.error(f"Error getting size of {path}: {e}")
316
+ return -1
317
+
318
+ def list_files(self, path: Union[str, Path],
319
+ pattern: str = '*',
320
+ recursive: bool = False,
321
+ include_dirs: bool = False) -> List[Path]:
322
+ """List files in directory with filtering.
323
+
324
+ Args:
325
+ path: Directory path
326
+ pattern: Glob pattern for filtering
327
+ recursive: Search recursively
328
+ include_dirs: Include directories in results
329
+
330
+ Returns:
331
+ List of matching paths
332
+ """
333
+ try:
334
+ path_obj = Path(path)
335
+ if not path_obj.is_dir():
336
+ logger.error(f"Not a directory: {path}")
337
+ return []
338
+
339
+ if recursive:
340
+ items = path_obj.rglob(pattern)
341
+ else:
342
+ items = path_obj.glob(pattern)
343
+
344
+ results = []
345
+ for item in items:
346
+ if item.is_file() or (include_dirs and item.is_dir()):
347
+ results.append(item)
348
+
349
+ return sorted(results)
350
+
351
+ except Exception as e:
352
+ logger.error(f"Error listing files in {path}: {e}")
353
+ return []
354
+
355
+
356
+ # Convenience instance for direct imports
357
+ path_ops = PathOperations()