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,744 @@
1
+ """
2
+ Conflict Resolution Manager - Basic conflict resolution for Version Control Agent.
3
+
4
+ This module provides conflict resolution management including:
5
+ 1. Conflict detection and analysis
6
+ 2. Automatic resolution strategies
7
+ 3. Manual resolution guidance
8
+ 4. Conflict prevention
9
+ 5. Resolution validation
10
+ """
11
+
12
+ import re
13
+ import difflib
14
+ from datetime import datetime
15
+ from pathlib import Path
16
+ from typing import Dict, List, Optional, Any, Tuple, Union
17
+ from dataclasses import dataclass, field
18
+ from enum import Enum
19
+ import logging
20
+
21
+
22
+ class ConflictType(Enum):
23
+ """Types of merge conflicts."""
24
+
25
+ CONTENT = "content"
26
+ BINARY = "binary"
27
+ DELETE_MODIFY = "delete_modify"
28
+ ADD_ADD = "add_add"
29
+ RENAME_RENAME = "rename_rename"
30
+ SUBMODULE = "submodule"
31
+
32
+
33
+ class ResolutionStrategy(Enum):
34
+ """Strategies for conflict resolution."""
35
+
36
+ MANUAL = "manual"
37
+ OURS = "ours"
38
+ THEIRS = "theirs"
39
+ AUTO_MERGE = "auto_merge"
40
+ SMART_MERGE = "smart_merge"
41
+
42
+
43
+ @dataclass
44
+ class ConflictMarker:
45
+ """Represents a conflict marker in a file."""
46
+
47
+ start_line: int
48
+ conflict_marker: int
49
+ end_line: int
50
+ ours_content: List[str]
51
+ theirs_content: List[str]
52
+ base_content: Optional[List[str]] = None
53
+
54
+
55
+ @dataclass
56
+ class FileConflict:
57
+ """Represents a conflict in a single file."""
58
+
59
+ file_path: str
60
+ conflict_type: ConflictType
61
+ markers: List[ConflictMarker] = field(default_factory=list)
62
+ our_version: Optional[str] = None
63
+ their_version: Optional[str] = None
64
+ base_version: Optional[str] = None
65
+ binary_conflict: bool = False
66
+ resolution_suggestion: Optional[str] = None
67
+ auto_resolvable: bool = False
68
+
69
+
70
+ @dataclass
71
+ class ConflictResolution:
72
+ """Represents a conflict resolution."""
73
+
74
+ file_path: str
75
+ strategy: ResolutionStrategy
76
+ resolved_content: Optional[str] = None
77
+ success: bool = False
78
+ message: str = ""
79
+ manual_intervention_required: bool = False
80
+ backup_created: bool = False
81
+
82
+
83
+ @dataclass
84
+ class ConflictAnalysis:
85
+ """Analysis of merge conflicts."""
86
+
87
+ total_conflicts: int
88
+ conflicted_files: List[str]
89
+ file_conflicts: List[FileConflict]
90
+ auto_resolvable_count: int
91
+ manual_resolution_count: int
92
+ conflict_severity: str # low, medium, high
93
+ resolution_complexity: str # simple, moderate, complex
94
+ estimated_resolution_time: int # minutes
95
+
96
+
97
+ class ConflictResolutionManager:
98
+ """
99
+ Manages conflict resolution for the Version Control Agent.
100
+
101
+ Provides conflict detection, analysis, and resolution capabilities
102
+ with both automatic and manual resolution strategies.
103
+ """
104
+
105
+ def __init__(self, project_root: str, logger: logging.Logger):
106
+ """
107
+ Initialize Conflict Resolution Manager.
108
+
109
+ Args:
110
+ project_root: Root directory of the project
111
+ logger: Logger instance
112
+ """
113
+ self.project_root = Path(project_root)
114
+ self.logger = logger
115
+
116
+ # Conflict markers
117
+ self.conflict_markers = {"start": r"^<{7} ", "separator": r"^={7}$", "end": r"^>{7} "}
118
+
119
+ # Auto-resolution patterns
120
+ self.auto_resolution_patterns = {
121
+ "whitespace_only": r"^\s*$",
122
+ "comment_only": r"^\s*[#//]",
123
+ "import_statement": r"^\s*(import|from|#include)",
124
+ "log_statement": r"(console\.log|print\(|logger\.|log\.)",
125
+ }
126
+
127
+ # File types that can be auto-resolved
128
+ self.auto_resolvable_extensions = {".md", ".txt", ".json", ".yml", ".yaml", ".xml"}
129
+
130
+ # Binary file extensions
131
+ self.binary_extensions = {
132
+ ".jpg",
133
+ ".jpeg",
134
+ ".png",
135
+ ".gif",
136
+ ".bmp",
137
+ ".ico",
138
+ ".pdf",
139
+ ".doc",
140
+ ".docx",
141
+ ".xls",
142
+ ".xlsx",
143
+ ".ppt",
144
+ ".pptx",
145
+ ".zip",
146
+ ".tar",
147
+ ".gz",
148
+ ".rar",
149
+ ".7z",
150
+ ".exe",
151
+ ".dll",
152
+ ".so",
153
+ ".dylib",
154
+ }
155
+
156
+ def detect_conflicts(self) -> ConflictAnalysis:
157
+ """
158
+ Detect merge conflicts in the repository.
159
+
160
+ Returns:
161
+ ConflictAnalysis with detailed conflict information
162
+ """
163
+ try:
164
+ # Get list of conflicted files from git status
165
+ conflicted_files = self._get_conflicted_files()
166
+
167
+ if not conflicted_files:
168
+ return ConflictAnalysis(
169
+ total_conflicts=0,
170
+ conflicted_files=[],
171
+ file_conflicts=[],
172
+ auto_resolvable_count=0,
173
+ manual_resolution_count=0,
174
+ conflict_severity="none",
175
+ resolution_complexity="none",
176
+ estimated_resolution_time=0,
177
+ )
178
+
179
+ # Analyze each conflicted file
180
+ file_conflicts = []
181
+ auto_resolvable_count = 0
182
+
183
+ for file_path in conflicted_files:
184
+ conflict = self._analyze_file_conflict(file_path)
185
+ file_conflicts.append(conflict)
186
+
187
+ if conflict.auto_resolvable:
188
+ auto_resolvable_count += 1
189
+
190
+ # Calculate analysis metrics
191
+ total_conflicts = len(file_conflicts)
192
+ manual_resolution_count = total_conflicts - auto_resolvable_count
193
+
194
+ # Determine severity and complexity
195
+ severity = self._calculate_severity(file_conflicts)
196
+ complexity = self._calculate_complexity(file_conflicts)
197
+ estimated_time = self._estimate_resolution_time(file_conflicts)
198
+
199
+ return ConflictAnalysis(
200
+ total_conflicts=total_conflicts,
201
+ conflicted_files=conflicted_files,
202
+ file_conflicts=file_conflicts,
203
+ auto_resolvable_count=auto_resolvable_count,
204
+ manual_resolution_count=manual_resolution_count,
205
+ conflict_severity=severity,
206
+ resolution_complexity=complexity,
207
+ estimated_resolution_time=estimated_time,
208
+ )
209
+
210
+ except Exception as e:
211
+ self.logger.error(f"Error detecting conflicts: {e}")
212
+ return ConflictAnalysis(
213
+ total_conflicts=0,
214
+ conflicted_files=[],
215
+ file_conflicts=[],
216
+ auto_resolvable_count=0,
217
+ manual_resolution_count=0,
218
+ conflict_severity="unknown",
219
+ resolution_complexity="unknown",
220
+ estimated_resolution_time=0,
221
+ )
222
+
223
+ def _get_conflicted_files(self) -> List[str]:
224
+ """Get list of files with merge conflicts."""
225
+ import subprocess
226
+
227
+ try:
228
+ # Use git status to find conflicted files
229
+ result = subprocess.run(
230
+ ["git", "status", "--porcelain"],
231
+ cwd=self.project_root,
232
+ capture_output=True,
233
+ text=True,
234
+ check=True,
235
+ )
236
+
237
+ conflicted_files = []
238
+ for line in result.stdout.strip().split("\n"):
239
+ if line.strip() and (
240
+ line.startswith("UU") or line.startswith("AA") or line.startswith("DD")
241
+ ):
242
+ # Extract filename
243
+ filename = line[3:].strip()
244
+ conflicted_files.append(filename)
245
+
246
+ return conflicted_files
247
+
248
+ except subprocess.CalledProcessError:
249
+ return []
250
+ except Exception as e:
251
+ self.logger.error(f"Error getting conflicted files: {e}")
252
+ return []
253
+
254
+ def _analyze_file_conflict(self, file_path: str) -> FileConflict:
255
+ """Analyze conflict in a single file."""
256
+ full_path = self.project_root / file_path
257
+
258
+ # Check if file is binary
259
+ if self._is_binary_file(full_path):
260
+ return FileConflict(
261
+ file_path=file_path,
262
+ conflict_type=ConflictType.BINARY,
263
+ binary_conflict=True,
264
+ auto_resolvable=False,
265
+ resolution_suggestion="Manual resolution required for binary file",
266
+ )
267
+
268
+ try:
269
+ with open(full_path, "r", encoding="utf-8") as f:
270
+ content = f.read()
271
+
272
+ # Parse conflict markers
273
+ markers = self._parse_conflict_markers(content)
274
+
275
+ if not markers:
276
+ # No conflict markers, might be add/add or delete/modify conflict
277
+ conflict_type = self._determine_conflict_type(file_path)
278
+ return FileConflict(
279
+ file_path=file_path,
280
+ conflict_type=conflict_type,
281
+ auto_resolvable=False,
282
+ resolution_suggestion="Check git status for conflict details",
283
+ )
284
+
285
+ # Determine if auto-resolvable
286
+ auto_resolvable = self._is_auto_resolvable(file_path, markers)
287
+
288
+ # Generate resolution suggestion
289
+ suggestion = self._generate_resolution_suggestion(file_path, markers)
290
+
291
+ return FileConflict(
292
+ file_path=file_path,
293
+ conflict_type=ConflictType.CONTENT,
294
+ markers=markers,
295
+ auto_resolvable=auto_resolvable,
296
+ resolution_suggestion=suggestion,
297
+ )
298
+
299
+ except Exception as e:
300
+ self.logger.error(f"Error analyzing conflict in {file_path}: {e}")
301
+ return FileConflict(
302
+ file_path=file_path,
303
+ conflict_type=ConflictType.CONTENT,
304
+ auto_resolvable=False,
305
+ resolution_suggestion=f"Error analyzing file: {e}",
306
+ )
307
+
308
+ def _is_binary_file(self, file_path: Path) -> bool:
309
+ """Check if file is binary."""
310
+ # Check extension
311
+ if file_path.suffix.lower() in self.binary_extensions:
312
+ return True
313
+
314
+ # Check content (simple heuristic)
315
+ try:
316
+ with open(file_path, "rb") as f:
317
+ chunk = f.read(1024)
318
+ if b"\0" in chunk:
319
+ return True
320
+ except Exception:
321
+ pass
322
+
323
+ return False
324
+
325
+ def _parse_conflict_markers(self, content: str) -> List[ConflictMarker]:
326
+ """Parse conflict markers from file content."""
327
+ lines = content.split("\n")
328
+ markers = []
329
+ i = 0
330
+
331
+ while i < len(lines):
332
+ line = lines[i]
333
+
334
+ # Look for conflict start marker
335
+ if re.match(self.conflict_markers["start"], line):
336
+ start_line = i
337
+ ours_content = []
338
+ theirs_content = []
339
+ conflict_marker = -1
340
+ end_line = -1
341
+
342
+ # Find separator and end markers
343
+ i += 1
344
+ while i < len(lines):
345
+ current_line = lines[i]
346
+
347
+ if re.match(self.conflict_markers["separator"], current_line):
348
+ conflict_marker = i
349
+ elif re.match(self.conflict_markers["end"], current_line):
350
+ end_line = i
351
+ break
352
+ elif conflict_marker == -1:
353
+ # We're in the "ours" section
354
+ ours_content.append(current_line)
355
+ else:
356
+ # We're in the "theirs" section
357
+ theirs_content.append(current_line)
358
+
359
+ i += 1
360
+
361
+ if conflict_marker != -1 and end_line != -1:
362
+ markers.append(
363
+ ConflictMarker(
364
+ start_line=start_line,
365
+ conflict_marker=conflict_marker,
366
+ end_line=end_line,
367
+ ours_content=ours_content,
368
+ theirs_content=theirs_content,
369
+ )
370
+ )
371
+
372
+ i += 1
373
+
374
+ return markers
375
+
376
+ def _determine_conflict_type(self, file_path: str) -> ConflictType:
377
+ """Determine the type of conflict for a file."""
378
+ # This would involve checking git status more carefully
379
+ # For now, default to content conflict
380
+ return ConflictType.CONTENT
381
+
382
+ def _is_auto_resolvable(self, file_path: str, markers: List[ConflictMarker]) -> bool:
383
+ """Determine if conflict can be automatically resolved."""
384
+ file_ext = Path(file_path).suffix.lower()
385
+
386
+ # Check if file type is auto-resolvable
387
+ if file_ext not in self.auto_resolvable_extensions:
388
+ return False
389
+
390
+ # Check if all conflicts are simple
391
+ for marker in markers:
392
+ if not self._is_simple_conflict(marker):
393
+ return False
394
+
395
+ return True
396
+
397
+ def _is_simple_conflict(self, marker: ConflictMarker) -> bool:
398
+ """Check if a conflict marker represents a simple conflict."""
399
+ ours_lines = marker.ours_content
400
+ theirs_lines = marker.theirs_content
401
+
402
+ # Check if one side is empty (addition vs no-change)
403
+ if not ours_lines or not theirs_lines:
404
+ return True
405
+
406
+ # Check if differences are only whitespace
407
+ if self._only_whitespace_differences(ours_lines, theirs_lines):
408
+ return True
409
+
410
+ # Check if differences are only comments
411
+ if self._only_comment_differences(ours_lines, theirs_lines):
412
+ return True
413
+
414
+ # Check if differences are only imports
415
+ if self._only_import_differences(ours_lines, theirs_lines):
416
+ return True
417
+
418
+ return False
419
+
420
+ def _only_whitespace_differences(self, ours: List[str], theirs: List[str]) -> bool:
421
+ """Check if differences are only whitespace."""
422
+ ours_stripped = [line.strip() for line in ours]
423
+ theirs_stripped = [line.strip() for line in theirs]
424
+ return ours_stripped == theirs_stripped
425
+
426
+ def _only_comment_differences(self, ours: List[str], theirs: List[str]) -> bool:
427
+ """Check if differences are only in comments."""
428
+ # This is a simplified check
429
+ for line in ours + theirs:
430
+ stripped = line.strip()
431
+ if stripped and not (stripped.startswith("#") or stripped.startswith("//")):
432
+ return False
433
+ return True
434
+
435
+ def _only_import_differences(self, ours: List[str], theirs: List[str]) -> bool:
436
+ """Check if differences are only in import statements."""
437
+ import_pattern = re.compile(r"^\s*(import|from|#include)")
438
+
439
+ for line in ours + theirs:
440
+ stripped = line.strip()
441
+ if stripped and not import_pattern.match(line):
442
+ return False
443
+ return True
444
+
445
+ def _generate_resolution_suggestion(self, file_path: str, markers: List[ConflictMarker]) -> str:
446
+ """Generate resolution suggestion for a file."""
447
+ if not markers:
448
+ return "No conflict markers found"
449
+
450
+ suggestions = []
451
+
452
+ for i, marker in enumerate(markers):
453
+ if not marker.ours_content and marker.theirs_content:
454
+ suggestions.append(f"Conflict {i+1}: Accept incoming changes (theirs)")
455
+ elif marker.ours_content and not marker.theirs_content:
456
+ suggestions.append(f"Conflict {i+1}: Keep current changes (ours)")
457
+ elif self._only_whitespace_differences(marker.ours_content, marker.theirs_content):
458
+ suggestions.append(
459
+ f"Conflict {i+1}: Whitespace differences only - can auto-resolve"
460
+ )
461
+ else:
462
+ suggestions.append(f"Conflict {i+1}: Manual merge required")
463
+
464
+ return "; ".join(suggestions)
465
+
466
+ def _calculate_severity(self, file_conflicts: List[FileConflict]) -> str:
467
+ """Calculate conflict severity."""
468
+ if not file_conflicts:
469
+ return "none"
470
+
471
+ binary_conflicts = sum(1 for fc in file_conflicts if fc.binary_conflict)
472
+ total_markers = sum(len(fc.markers) for fc in file_conflicts)
473
+ auto_resolvable = sum(1 for fc in file_conflicts if fc.auto_resolvable)
474
+
475
+ # High severity: binary conflicts or many complex conflicts
476
+ if binary_conflicts > 0 or (
477
+ total_markers > 10 and auto_resolvable < len(file_conflicts) * 0.5
478
+ ):
479
+ return "high"
480
+
481
+ # Medium severity: some conflicts require manual resolution
482
+ if total_markers > 5 or auto_resolvable < len(file_conflicts) * 0.8:
483
+ return "medium"
484
+
485
+ # Low severity: mostly auto-resolvable
486
+ return "low"
487
+
488
+ def _calculate_complexity(self, file_conflicts: List[FileConflict]) -> str:
489
+ """Calculate resolution complexity."""
490
+ if not file_conflicts:
491
+ return "none"
492
+
493
+ total_conflicts = len(file_conflicts)
494
+ auto_resolvable = sum(1 for fc in file_conflicts if fc.auto_resolvable)
495
+ auto_ratio = auto_resolvable / total_conflicts
496
+
497
+ if auto_ratio > 0.8:
498
+ return "simple"
499
+ elif auto_ratio > 0.5:
500
+ return "moderate"
501
+ else:
502
+ return "complex"
503
+
504
+ def _estimate_resolution_time(self, file_conflicts: List[FileConflict]) -> int:
505
+ """Estimate resolution time in minutes."""
506
+ if not file_conflicts:
507
+ return 0
508
+
509
+ total_time = 0
510
+
511
+ for conflict in file_conflicts:
512
+ if conflict.auto_resolvable:
513
+ total_time += 1 # 1 minute for auto-resolution
514
+ elif conflict.binary_conflict:
515
+ total_time += 15 # 15 minutes for binary conflicts
516
+ else:
517
+ # Time based on number of conflict markers
518
+ markers_count = len(conflict.markers)
519
+ total_time += min(5 + markers_count * 2, 30) # 5-30 minutes per file
520
+
521
+ return total_time
522
+
523
+ def resolve_conflicts_automatically(
524
+ self,
525
+ file_conflicts: List[FileConflict],
526
+ strategy: ResolutionStrategy = ResolutionStrategy.AUTO_MERGE,
527
+ ) -> List[ConflictResolution]:
528
+ """
529
+ Automatically resolve conflicts where possible.
530
+
531
+ Args:
532
+ file_conflicts: List of file conflicts to resolve
533
+ strategy: Resolution strategy to use
534
+
535
+ Returns:
536
+ List of resolution results
537
+ """
538
+ resolutions = []
539
+
540
+ for conflict in file_conflicts:
541
+ if not conflict.auto_resolvable:
542
+ resolutions.append(
543
+ ConflictResolution(
544
+ file_path=conflict.file_path,
545
+ strategy=ResolutionStrategy.MANUAL,
546
+ success=False,
547
+ message="Requires manual resolution",
548
+ manual_intervention_required=True,
549
+ )
550
+ )
551
+ continue
552
+
553
+ try:
554
+ resolution = self._resolve_file_conflict(conflict, strategy)
555
+ resolutions.append(resolution)
556
+
557
+ except Exception as e:
558
+ self.logger.error(f"Error resolving conflict in {conflict.file_path}: {e}")
559
+ resolutions.append(
560
+ ConflictResolution(
561
+ file_path=conflict.file_path,
562
+ strategy=strategy,
563
+ success=False,
564
+ message=f"Auto-resolution failed: {e}",
565
+ manual_intervention_required=True,
566
+ )
567
+ )
568
+
569
+ return resolutions
570
+
571
+ def _resolve_file_conflict(
572
+ self, conflict: FileConflict, strategy: ResolutionStrategy
573
+ ) -> ConflictResolution:
574
+ """Resolve conflict in a single file."""
575
+ file_path = self.project_root / conflict.file_path
576
+
577
+ # Create backup
578
+ backup_created = self._create_backup(file_path)
579
+
580
+ try:
581
+ # Read current content
582
+ with open(file_path, "r", encoding="utf-8") as f:
583
+ content = f.read()
584
+
585
+ # Apply resolution strategy
586
+ resolved_content = self._apply_resolution_strategy(content, conflict.markers, strategy)
587
+
588
+ # Write resolved content
589
+ with open(file_path, "w", encoding="utf-8") as f:
590
+ f.write(resolved_content)
591
+
592
+ return ConflictResolution(
593
+ file_path=conflict.file_path,
594
+ strategy=strategy,
595
+ resolved_content=resolved_content,
596
+ success=True,
597
+ message="Successfully auto-resolved",
598
+ backup_created=backup_created,
599
+ )
600
+
601
+ except Exception as e:
602
+ return ConflictResolution(
603
+ file_path=conflict.file_path,
604
+ strategy=strategy,
605
+ success=False,
606
+ message=f"Resolution failed: {e}",
607
+ manual_intervention_required=True,
608
+ backup_created=backup_created,
609
+ )
610
+
611
+ def _create_backup(self, file_path: Path) -> bool:
612
+ """Create backup of file before resolution."""
613
+ try:
614
+ backup_path = file_path.with_suffix(f"{file_path.suffix}.conflict_backup")
615
+ backup_path.write_text(file_path.read_text())
616
+ return True
617
+ except Exception as e:
618
+ self.logger.warning(f"Could not create backup for {file_path}: {e}")
619
+ return False
620
+
621
+ def _apply_resolution_strategy(
622
+ self, content: str, markers: List[ConflictMarker], strategy: ResolutionStrategy
623
+ ) -> str:
624
+ """Apply resolution strategy to content."""
625
+ lines = content.split("\n")
626
+
627
+ # Process markers in reverse order to preserve line numbers
628
+ for marker in reversed(markers):
629
+ if strategy == ResolutionStrategy.OURS:
630
+ # Keep our version
631
+ replacement = marker.ours_content
632
+ elif strategy == ResolutionStrategy.THEIRS:
633
+ # Keep their version
634
+ replacement = marker.theirs_content
635
+ elif strategy == ResolutionStrategy.AUTO_MERGE:
636
+ # Smart merge
637
+ replacement = self._smart_merge(marker)
638
+ else:
639
+ # Default to ours
640
+ replacement = marker.ours_content
641
+
642
+ # Replace the conflict section
643
+ lines[marker.start_line : marker.end_line + 1] = replacement
644
+
645
+ return "\n".join(lines)
646
+
647
+ def _smart_merge(self, marker: ConflictMarker) -> List[str]:
648
+ """Perform smart merge of conflict marker."""
649
+ ours = marker.ours_content
650
+ theirs = marker.theirs_content
651
+
652
+ # If one side is empty, use the other
653
+ if not ours:
654
+ return theirs
655
+ if not theirs:
656
+ return ours
657
+
658
+ # If only whitespace differences, use ours but with their spacing
659
+ if self._only_whitespace_differences(ours, theirs):
660
+ return theirs # Keep their formatting
661
+
662
+ # If only comment differences, merge both
663
+ if self._only_comment_differences(ours, theirs):
664
+ return ours + theirs
665
+
666
+ # If only import differences, merge and sort
667
+ if self._only_import_differences(ours, theirs):
668
+ combined = list(set(ours + theirs))
669
+ return sorted(combined)
670
+
671
+ # Default: keep ours
672
+ return ours
673
+
674
+ def generate_resolution_report(self, analysis: ConflictAnalysis) -> str:
675
+ """Generate a human-readable conflict resolution report."""
676
+ if analysis.total_conflicts == 0:
677
+ return "No merge conflicts detected."
678
+
679
+ report_lines = [
680
+ "Merge Conflict Analysis Report",
681
+ "=" * 40,
682
+ "",
683
+ f"Total conflicts: {analysis.total_conflicts}",
684
+ f"Conflicted files: {len(analysis.conflicted_files)}",
685
+ f"Auto-resolvable: {analysis.auto_resolvable_count}",
686
+ f"Manual resolution required: {analysis.manual_resolution_count}",
687
+ f"Severity: {analysis.conflict_severity}",
688
+ f"Complexity: {analysis.resolution_complexity}",
689
+ f"Estimated resolution time: {analysis.estimated_resolution_time} minutes",
690
+ "",
691
+ "Conflicted Files:",
692
+ "-" * 20,
693
+ ]
694
+
695
+ for conflict in analysis.file_conflicts:
696
+ report_lines.append(f"• {conflict.file_path}")
697
+ report_lines.append(f" Type: {conflict.conflict_type.value}")
698
+ if conflict.auto_resolvable:
699
+ report_lines.append(" Status: Auto-resolvable")
700
+ else:
701
+ report_lines.append(" Status: Manual resolution required")
702
+
703
+ if conflict.resolution_suggestion:
704
+ report_lines.append(f" Suggestion: {conflict.resolution_suggestion}")
705
+
706
+ report_lines.append("")
707
+
708
+ return "\n".join(report_lines)
709
+
710
+ def get_resolution_guidance(self, file_path: str) -> Dict[str, Any]:
711
+ """Get detailed resolution guidance for a specific file."""
712
+ full_path = self.project_root / file_path
713
+
714
+ if not full_path.exists():
715
+ return {"error": f"File not found: {file_path}"}
716
+
717
+ try:
718
+ conflict = self._analyze_file_conflict(file_path)
719
+
720
+ guidance = {
721
+ "file_path": file_path,
722
+ "conflict_type": conflict.conflict_type.value,
723
+ "auto_resolvable": conflict.auto_resolvable,
724
+ "resolution_suggestion": conflict.resolution_suggestion,
725
+ "markers": [],
726
+ }
727
+
728
+ for i, marker in enumerate(conflict.markers):
729
+ marker_info = {
730
+ "conflict_number": i + 1,
731
+ "lines": f"{marker.start_line + 1}-{marker.end_line + 1}",
732
+ "ours_lines": len(marker.ours_content),
733
+ "theirs_lines": len(marker.theirs_content),
734
+ "preview": {
735
+ "ours": marker.ours_content[:3], # First 3 lines
736
+ "theirs": marker.theirs_content[:3],
737
+ },
738
+ }
739
+ guidance["markers"].append(marker_info)
740
+
741
+ return guidance
742
+
743
+ except Exception as e:
744
+ return {"error": f"Error analyzing file: {e}"}