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.
- claude_mpm/__init__.py +17 -0
- claude_mpm/__main__.py +14 -0
- claude_mpm/_version.py +32 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +88 -0
- claude_mpm/agents/INSTRUCTIONS.md +375 -0
- claude_mpm/agents/__init__.py +118 -0
- claude_mpm/agents/agent_loader.py +621 -0
- claude_mpm/agents/agent_loader_integration.py +229 -0
- claude_mpm/agents/agents_metadata.py +204 -0
- claude_mpm/agents/base_agent.json +27 -0
- claude_mpm/agents/base_agent_loader.py +519 -0
- claude_mpm/agents/schema/agent_schema.json +160 -0
- claude_mpm/agents/system_agent_config.py +587 -0
- claude_mpm/agents/templates/__init__.py +101 -0
- claude_mpm/agents/templates/data_engineer_agent.json +46 -0
- claude_mpm/agents/templates/documentation_agent.json +45 -0
- claude_mpm/agents/templates/engineer_agent.json +49 -0
- claude_mpm/agents/templates/ops_agent.json +46 -0
- claude_mpm/agents/templates/qa_agent.json +45 -0
- claude_mpm/agents/templates/research_agent.json +49 -0
- claude_mpm/agents/templates/security_agent.json +46 -0
- claude_mpm/agents/templates/update-optimized-specialized-agents.json +374 -0
- claude_mpm/agents/templates/version_control_agent.json +46 -0
- claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +6 -0
- claude_mpm/cli.py +655 -0
- claude_mpm/cli_main.py +13 -0
- claude_mpm/cli_module/__init__.py +15 -0
- claude_mpm/cli_module/args.py +222 -0
- claude_mpm/cli_module/commands.py +203 -0
- claude_mpm/cli_module/migration_example.py +183 -0
- claude_mpm/cli_module/refactoring_guide.md +253 -0
- claude_mpm/cli_old/__init__.py +1 -0
- claude_mpm/cli_old/ticket_cli.py +102 -0
- claude_mpm/config/__init__.py +5 -0
- claude_mpm/config/hook_config.py +42 -0
- claude_mpm/constants.py +150 -0
- claude_mpm/core/__init__.py +45 -0
- claude_mpm/core/agent_name_normalizer.py +248 -0
- claude_mpm/core/agent_registry.py +627 -0
- claude_mpm/core/agent_registry.py.bak +312 -0
- claude_mpm/core/agent_session_manager.py +273 -0
- claude_mpm/core/base_service.py +747 -0
- claude_mpm/core/base_service.py.bak +406 -0
- claude_mpm/core/config.py +334 -0
- claude_mpm/core/config_aliases.py +292 -0
- claude_mpm/core/container.py +347 -0
- claude_mpm/core/factories.py +281 -0
- claude_mpm/core/framework_loader.py +472 -0
- claude_mpm/core/injectable_service.py +206 -0
- claude_mpm/core/interfaces.py +539 -0
- claude_mpm/core/logger.py +468 -0
- claude_mpm/core/minimal_framework_loader.py +107 -0
- claude_mpm/core/mixins.py +150 -0
- claude_mpm/core/service_registry.py +299 -0
- claude_mpm/core/session_manager.py +190 -0
- claude_mpm/core/simple_runner.py +511 -0
- claude_mpm/core/tool_access_control.py +173 -0
- claude_mpm/hooks/README.md +243 -0
- claude_mpm/hooks/__init__.py +5 -0
- claude_mpm/hooks/base_hook.py +154 -0
- claude_mpm/hooks/builtin/__init__.py +1 -0
- claude_mpm/hooks/builtin/logging_hook_example.py +165 -0
- claude_mpm/hooks/builtin/post_delegation_hook_example.py +124 -0
- claude_mpm/hooks/builtin/pre_delegation_hook_example.py +125 -0
- claude_mpm/hooks/builtin/submit_hook_example.py +100 -0
- claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +237 -0
- claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +239 -0
- claude_mpm/hooks/builtin/workflow_start_hook.py +181 -0
- claude_mpm/hooks/hook_client.py +264 -0
- claude_mpm/hooks/hook_runner.py +370 -0
- claude_mpm/hooks/json_rpc_executor.py +259 -0
- claude_mpm/hooks/json_rpc_hook_client.py +319 -0
- claude_mpm/hooks/tool_call_interceptor.py +204 -0
- claude_mpm/init.py +246 -0
- claude_mpm/orchestration/SUBPROCESS_DESIGN.md +66 -0
- claude_mpm/orchestration/__init__.py +6 -0
- claude_mpm/orchestration/archive/direct_orchestrator.py +195 -0
- claude_mpm/orchestration/archive/factory.py +215 -0
- claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +188 -0
- claude_mpm/orchestration/archive/hook_integration_example.py +178 -0
- claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +826 -0
- claude_mpm/orchestration/archive/orchestrator.py +501 -0
- claude_mpm/orchestration/archive/pexpect_orchestrator.py +252 -0
- claude_mpm/orchestration/archive/pty_orchestrator.py +270 -0
- claude_mpm/orchestration/archive/simple_orchestrator.py +82 -0
- claude_mpm/orchestration/archive/subprocess_orchestrator.py +801 -0
- claude_mpm/orchestration/archive/system_prompt_orchestrator.py +278 -0
- claude_mpm/orchestration/archive/wrapper_orchestrator.py +187 -0
- claude_mpm/scripts/__init__.py +1 -0
- claude_mpm/scripts/ticket.py +269 -0
- claude_mpm/services/__init__.py +10 -0
- claude_mpm/services/agent_deployment.py +955 -0
- claude_mpm/services/agent_lifecycle_manager.py +948 -0
- claude_mpm/services/agent_management_service.py +596 -0
- claude_mpm/services/agent_modification_tracker.py +841 -0
- claude_mpm/services/agent_profile_loader.py +606 -0
- claude_mpm/services/agent_registry.py +677 -0
- claude_mpm/services/base_agent_manager.py +380 -0
- claude_mpm/services/framework_agent_loader.py +337 -0
- claude_mpm/services/framework_claude_md_generator/README.md +92 -0
- claude_mpm/services/framework_claude_md_generator/__init__.py +206 -0
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +151 -0
- claude_mpm/services/framework_claude_md_generator/content_validator.py +126 -0
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +137 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +106 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +582 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +97 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +27 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +23 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +23 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +20 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +26 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +30 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +37 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +111 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +89 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +39 -0
- claude_mpm/services/framework_claude_md_generator/section_manager.py +106 -0
- claude_mpm/services/framework_claude_md_generator/version_manager.py +121 -0
- claude_mpm/services/framework_claude_md_generator.py +621 -0
- claude_mpm/services/hook_service.py +388 -0
- claude_mpm/services/hook_service_manager.py +223 -0
- claude_mpm/services/json_rpc_hook_manager.py +92 -0
- claude_mpm/services/parent_directory_manager/README.md +83 -0
- claude_mpm/services/parent_directory_manager/__init__.py +577 -0
- claude_mpm/services/parent_directory_manager/backup_manager.py +258 -0
- claude_mpm/services/parent_directory_manager/config_manager.py +210 -0
- claude_mpm/services/parent_directory_manager/deduplication_manager.py +279 -0
- claude_mpm/services/parent_directory_manager/framework_protector.py +143 -0
- claude_mpm/services/parent_directory_manager/operations.py +186 -0
- claude_mpm/services/parent_directory_manager/state_manager.py +624 -0
- claude_mpm/services/parent_directory_manager/template_deployer.py +579 -0
- claude_mpm/services/parent_directory_manager/validation_manager.py +378 -0
- claude_mpm/services/parent_directory_manager/version_control_helper.py +339 -0
- claude_mpm/services/parent_directory_manager/version_manager.py +222 -0
- claude_mpm/services/shared_prompt_cache.py +819 -0
- claude_mpm/services/ticket_manager.py +213 -0
- claude_mpm/services/ticket_manager_di.py +318 -0
- claude_mpm/services/ticketing_service_original.py +508 -0
- claude_mpm/services/version_control/VERSION +1 -0
- claude_mpm/services/version_control/__init__.py +70 -0
- claude_mpm/services/version_control/branch_strategy.py +670 -0
- claude_mpm/services/version_control/conflict_resolution.py +744 -0
- claude_mpm/services/version_control/git_operations.py +784 -0
- claude_mpm/services/version_control/semantic_versioning.py +703 -0
- claude_mpm/ui/__init__.py +1 -0
- claude_mpm/ui/rich_terminal_ui.py +295 -0
- claude_mpm/ui/terminal_ui.py +328 -0
- claude_mpm/utils/__init__.py +16 -0
- claude_mpm/utils/config_manager.py +468 -0
- claude_mpm/utils/import_migration_example.py +80 -0
- claude_mpm/utils/imports.py +182 -0
- claude_mpm/utils/path_operations.py +357 -0
- claude_mpm/utils/paths.py +289 -0
- claude_mpm-0.3.0.dist-info/METADATA +290 -0
- claude_mpm-0.3.0.dist-info/RECORD +159 -0
- claude_mpm-0.3.0.dist-info/WHEEL +5 -0
- claude_mpm-0.3.0.dist-info/entry_points.txt +4 -0
- claude_mpm-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Branch Strategy Manager - Branch strategy implementations for Version Control Agent.
|
|
3
|
+
|
|
4
|
+
This module provides comprehensive branch strategy management including:
|
|
5
|
+
1. Issue-driven development workflow
|
|
6
|
+
2. GitFlow implementation
|
|
7
|
+
3. GitHub Flow implementation
|
|
8
|
+
4. Custom branch strategies
|
|
9
|
+
5. Branch lifecycle management
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Dict, List, Optional, Any, Tuple
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
import logging
|
|
18
|
+
import re
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BranchStrategyType(Enum):
|
|
22
|
+
"""Types of branch strategies."""
|
|
23
|
+
|
|
24
|
+
ISSUE_DRIVEN = "issue_driven"
|
|
25
|
+
GITFLOW = "gitflow"
|
|
26
|
+
GITHUB_FLOW = "github_flow"
|
|
27
|
+
CUSTOM = "custom"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BranchType(Enum):
|
|
31
|
+
"""Types of branches in different strategies."""
|
|
32
|
+
|
|
33
|
+
MAIN = "main"
|
|
34
|
+
DEVELOP = "develop"
|
|
35
|
+
FEATURE = "feature"
|
|
36
|
+
ISSUE = "issue"
|
|
37
|
+
ENHANCEMENT = "enhancement"
|
|
38
|
+
HOTFIX = "hotfix"
|
|
39
|
+
RELEASE = "release"
|
|
40
|
+
EPIC = "epic"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class BranchNamingRule:
|
|
45
|
+
"""Rules for branch naming."""
|
|
46
|
+
|
|
47
|
+
branch_type: BranchType
|
|
48
|
+
prefix: str
|
|
49
|
+
pattern: str
|
|
50
|
+
required_fields: List[str] = field(default_factory=list)
|
|
51
|
+
max_length: Optional[int] = None
|
|
52
|
+
description: str = ""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class BranchLifecycleRule:
|
|
57
|
+
"""Rules for branch lifecycle management."""
|
|
58
|
+
|
|
59
|
+
branch_type: BranchType
|
|
60
|
+
auto_create: bool = False
|
|
61
|
+
auto_merge_target: Optional[str] = None
|
|
62
|
+
auto_merge_strategy: str = "merge"
|
|
63
|
+
auto_delete_after_merge: bool = False
|
|
64
|
+
requires_qa_approval: bool = False
|
|
65
|
+
requires_review: bool = False
|
|
66
|
+
merge_message_template: Optional[str] = None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class BranchWorkflow:
|
|
71
|
+
"""Workflow definition for a branch strategy."""
|
|
72
|
+
|
|
73
|
+
strategy_type: BranchStrategyType
|
|
74
|
+
main_branch: str
|
|
75
|
+
development_branch: Optional[str] = None
|
|
76
|
+
naming_rules: List[BranchNamingRule] = field(default_factory=list)
|
|
77
|
+
lifecycle_rules: List[BranchLifecycleRule] = field(default_factory=list)
|
|
78
|
+
merge_targets: Dict[str, str] = field(default_factory=dict)
|
|
79
|
+
quality_gates: List[str] = field(default_factory=list)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class BranchStrategyManager:
|
|
83
|
+
"""
|
|
84
|
+
Manages branch strategies for the Version Control Agent.
|
|
85
|
+
|
|
86
|
+
Provides implementation of different branching strategies including
|
|
87
|
+
issue-driven development, GitFlow, and GitHub Flow.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def __init__(self, project_root: str, logger: logging.Logger):
|
|
91
|
+
"""
|
|
92
|
+
Initialize Branch Strategy Manager.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
project_root: Root directory of the project
|
|
96
|
+
logger: Logger instance
|
|
97
|
+
"""
|
|
98
|
+
self.project_root = Path(project_root)
|
|
99
|
+
self.logger = logger
|
|
100
|
+
|
|
101
|
+
# Initialize predefined strategies
|
|
102
|
+
self.strategies = {
|
|
103
|
+
BranchStrategyType.ISSUE_DRIVEN: self._create_issue_driven_strategy(),
|
|
104
|
+
BranchStrategyType.GITFLOW: self._create_gitflow_strategy(),
|
|
105
|
+
BranchStrategyType.GITHUB_FLOW: self._create_github_flow_strategy(),
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# Current strategy
|
|
109
|
+
self.current_strategy: Optional[BranchWorkflow] = None
|
|
110
|
+
|
|
111
|
+
# Load strategy from configuration
|
|
112
|
+
self._load_strategy_configuration()
|
|
113
|
+
|
|
114
|
+
def _create_issue_driven_strategy(self) -> BranchWorkflow:
|
|
115
|
+
"""Create issue-driven development strategy."""
|
|
116
|
+
naming_rules = [
|
|
117
|
+
BranchNamingRule(
|
|
118
|
+
branch_type=BranchType.ISSUE,
|
|
119
|
+
prefix="issue/",
|
|
120
|
+
pattern=r"^issue/[A-Z]+-\d+(-.*)?$",
|
|
121
|
+
required_fields=["ticket_id"],
|
|
122
|
+
description="Issue branches for bug fixes and standard issues",
|
|
123
|
+
),
|
|
124
|
+
BranchNamingRule(
|
|
125
|
+
branch_type=BranchType.FEATURE,
|
|
126
|
+
prefix="feature/",
|
|
127
|
+
pattern=r"^feature/[A-Z]+-\d+(-.*)?$",
|
|
128
|
+
required_fields=["ticket_id"],
|
|
129
|
+
description="Feature branches for new functionality",
|
|
130
|
+
),
|
|
131
|
+
BranchNamingRule(
|
|
132
|
+
branch_type=BranchType.ENHANCEMENT,
|
|
133
|
+
prefix="enhancement/",
|
|
134
|
+
pattern=r"^enhancement/[A-Z]+-\d+(-.*)?$",
|
|
135
|
+
required_fields=["ticket_id"],
|
|
136
|
+
description="Enhancement branches for improvements",
|
|
137
|
+
),
|
|
138
|
+
BranchNamingRule(
|
|
139
|
+
branch_type=BranchType.HOTFIX,
|
|
140
|
+
prefix="hotfix/",
|
|
141
|
+
pattern=r"^hotfix/[A-Z]+-\d+(-.*)?$",
|
|
142
|
+
required_fields=["ticket_id"],
|
|
143
|
+
description="Hotfix branches for critical fixes",
|
|
144
|
+
),
|
|
145
|
+
BranchNamingRule(
|
|
146
|
+
branch_type=BranchType.EPIC,
|
|
147
|
+
prefix="epic/",
|
|
148
|
+
pattern=r"^epic/[A-Z]+-\d+(-.*)?$",
|
|
149
|
+
required_fields=["epic_id"],
|
|
150
|
+
description="Epic branches for large features",
|
|
151
|
+
),
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
lifecycle_rules = [
|
|
155
|
+
BranchLifecycleRule(
|
|
156
|
+
branch_type=BranchType.ISSUE,
|
|
157
|
+
auto_merge_target="main",
|
|
158
|
+
auto_merge_strategy="merge",
|
|
159
|
+
auto_delete_after_merge=True,
|
|
160
|
+
requires_qa_approval=True,
|
|
161
|
+
merge_message_template="Merge {branch_name}: {ticket_title}",
|
|
162
|
+
),
|
|
163
|
+
BranchLifecycleRule(
|
|
164
|
+
branch_type=BranchType.FEATURE,
|
|
165
|
+
auto_merge_target="main",
|
|
166
|
+
auto_merge_strategy="merge",
|
|
167
|
+
auto_delete_after_merge=True,
|
|
168
|
+
requires_qa_approval=True,
|
|
169
|
+
requires_review=True,
|
|
170
|
+
merge_message_template="Merge {branch_name}: {ticket_title}",
|
|
171
|
+
),
|
|
172
|
+
BranchLifecycleRule(
|
|
173
|
+
branch_type=BranchType.ENHANCEMENT,
|
|
174
|
+
auto_merge_target="main",
|
|
175
|
+
auto_merge_strategy="merge",
|
|
176
|
+
auto_delete_after_merge=True,
|
|
177
|
+
requires_qa_approval=True,
|
|
178
|
+
merge_message_template="Merge {branch_name}: {ticket_title}",
|
|
179
|
+
),
|
|
180
|
+
BranchLifecycleRule(
|
|
181
|
+
branch_type=BranchType.HOTFIX,
|
|
182
|
+
auto_merge_target="main",
|
|
183
|
+
auto_merge_strategy="merge",
|
|
184
|
+
auto_delete_after_merge=True,
|
|
185
|
+
requires_qa_approval=False, # Hotfixes can be fast-tracked
|
|
186
|
+
merge_message_template="Hotfix {branch_name}: {ticket_title}",
|
|
187
|
+
),
|
|
188
|
+
BranchLifecycleRule(
|
|
189
|
+
branch_type=BranchType.EPIC,
|
|
190
|
+
auto_merge_target="main",
|
|
191
|
+
auto_merge_strategy="squash",
|
|
192
|
+
auto_delete_after_merge=True,
|
|
193
|
+
requires_qa_approval=True,
|
|
194
|
+
requires_review=True,
|
|
195
|
+
merge_message_template="Complete epic {branch_name}: {epic_title}",
|
|
196
|
+
),
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
return BranchWorkflow(
|
|
200
|
+
strategy_type=BranchStrategyType.ISSUE_DRIVEN,
|
|
201
|
+
main_branch="main",
|
|
202
|
+
naming_rules=naming_rules,
|
|
203
|
+
lifecycle_rules=lifecycle_rules,
|
|
204
|
+
merge_targets={
|
|
205
|
+
"issue/*": "main",
|
|
206
|
+
"feature/*": "main",
|
|
207
|
+
"enhancement/*": "main",
|
|
208
|
+
"hotfix/*": "main",
|
|
209
|
+
"epic/*": "main",
|
|
210
|
+
},
|
|
211
|
+
quality_gates=["documentation_validation", "qa_testing", "code_quality"],
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def _create_gitflow_strategy(self) -> BranchWorkflow:
|
|
215
|
+
"""Create GitFlow strategy."""
|
|
216
|
+
naming_rules = [
|
|
217
|
+
BranchNamingRule(
|
|
218
|
+
branch_type=BranchType.FEATURE,
|
|
219
|
+
prefix="feature/",
|
|
220
|
+
pattern=r"^feature/.*$",
|
|
221
|
+
description="Feature branches for new functionality",
|
|
222
|
+
),
|
|
223
|
+
BranchNamingRule(
|
|
224
|
+
branch_type=BranchType.RELEASE,
|
|
225
|
+
prefix="release/",
|
|
226
|
+
pattern=r"^release/\d+\.\d+(\.\d+)?$",
|
|
227
|
+
required_fields=["version"],
|
|
228
|
+
description="Release branches for version preparation",
|
|
229
|
+
),
|
|
230
|
+
BranchNamingRule(
|
|
231
|
+
branch_type=BranchType.HOTFIX,
|
|
232
|
+
prefix="hotfix/",
|
|
233
|
+
pattern=r"^hotfix/.*$",
|
|
234
|
+
description="Hotfix branches for critical fixes",
|
|
235
|
+
),
|
|
236
|
+
]
|
|
237
|
+
|
|
238
|
+
lifecycle_rules = [
|
|
239
|
+
BranchLifecycleRule(
|
|
240
|
+
branch_type=BranchType.FEATURE,
|
|
241
|
+
auto_merge_target="develop",
|
|
242
|
+
auto_merge_strategy="merge",
|
|
243
|
+
auto_delete_after_merge=True,
|
|
244
|
+
requires_review=True,
|
|
245
|
+
),
|
|
246
|
+
BranchLifecycleRule(
|
|
247
|
+
branch_type=BranchType.RELEASE,
|
|
248
|
+
auto_merge_target="main",
|
|
249
|
+
auto_merge_strategy="merge",
|
|
250
|
+
auto_delete_after_merge=True,
|
|
251
|
+
requires_qa_approval=True,
|
|
252
|
+
),
|
|
253
|
+
BranchLifecycleRule(
|
|
254
|
+
branch_type=BranchType.HOTFIX,
|
|
255
|
+
auto_merge_target="main",
|
|
256
|
+
auto_merge_strategy="merge",
|
|
257
|
+
auto_delete_after_merge=True,
|
|
258
|
+
),
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
return BranchWorkflow(
|
|
262
|
+
strategy_type=BranchStrategyType.GITFLOW,
|
|
263
|
+
main_branch="main",
|
|
264
|
+
development_branch="develop",
|
|
265
|
+
naming_rules=naming_rules,
|
|
266
|
+
lifecycle_rules=lifecycle_rules,
|
|
267
|
+
merge_targets={"feature/*": "develop", "release/*": "main", "hotfix/*": "main"},
|
|
268
|
+
quality_gates=["testing", "code_review"],
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
def _create_github_flow_strategy(self) -> BranchWorkflow:
|
|
272
|
+
"""Create GitHub Flow strategy."""
|
|
273
|
+
naming_rules = [
|
|
274
|
+
BranchNamingRule(
|
|
275
|
+
branch_type=BranchType.FEATURE,
|
|
276
|
+
prefix="",
|
|
277
|
+
pattern=r"^[a-z0-9\-/]+$",
|
|
278
|
+
description="Feature branches with descriptive names",
|
|
279
|
+
)
|
|
280
|
+
]
|
|
281
|
+
|
|
282
|
+
lifecycle_rules = [
|
|
283
|
+
BranchLifecycleRule(
|
|
284
|
+
branch_type=BranchType.FEATURE,
|
|
285
|
+
auto_merge_target="main",
|
|
286
|
+
auto_merge_strategy="squash",
|
|
287
|
+
auto_delete_after_merge=True,
|
|
288
|
+
requires_review=True,
|
|
289
|
+
requires_qa_approval=True,
|
|
290
|
+
)
|
|
291
|
+
]
|
|
292
|
+
|
|
293
|
+
return BranchWorkflow(
|
|
294
|
+
strategy_type=BranchStrategyType.GITHUB_FLOW,
|
|
295
|
+
main_branch="main",
|
|
296
|
+
naming_rules=naming_rules,
|
|
297
|
+
lifecycle_rules=lifecycle_rules,
|
|
298
|
+
merge_targets={"*": "main"},
|
|
299
|
+
quality_gates=["ci_tests", "code_review", "deployment_test"],
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
def _load_strategy_configuration(self) -> None:
|
|
303
|
+
"""Load strategy configuration from project files."""
|
|
304
|
+
# Try to load from configuration files
|
|
305
|
+
config_files = [
|
|
306
|
+
".claude-pm/config.json",
|
|
307
|
+
"workflow.md",
|
|
308
|
+
".github/workflows/branch-strategy.yml",
|
|
309
|
+
"branch-strategy.json",
|
|
310
|
+
]
|
|
311
|
+
|
|
312
|
+
for config_file in config_files:
|
|
313
|
+
config_path = self.project_root / config_file
|
|
314
|
+
if config_path.exists():
|
|
315
|
+
try:
|
|
316
|
+
strategy = self._parse_strategy_config(config_path)
|
|
317
|
+
if strategy:
|
|
318
|
+
self.current_strategy = strategy
|
|
319
|
+
self.logger.info(f"Loaded branch strategy from {config_file}")
|
|
320
|
+
return
|
|
321
|
+
except Exception as e:
|
|
322
|
+
self.logger.error(f"Error loading strategy from {config_file}: {e}")
|
|
323
|
+
|
|
324
|
+
# Default to issue-driven strategy
|
|
325
|
+
self.current_strategy = self.strategies[BranchStrategyType.ISSUE_DRIVEN]
|
|
326
|
+
self.logger.info("Using default issue-driven branch strategy")
|
|
327
|
+
|
|
328
|
+
def _parse_strategy_config(self, config_path: Path) -> Optional[BranchWorkflow]:
|
|
329
|
+
"""Parse strategy configuration from file."""
|
|
330
|
+
# This would implement parsing logic for different config file formats
|
|
331
|
+
# For now, return None to use defaults
|
|
332
|
+
return None
|
|
333
|
+
|
|
334
|
+
def get_current_strategy(self) -> BranchWorkflow:
|
|
335
|
+
"""Get the current branch strategy."""
|
|
336
|
+
if not self.current_strategy:
|
|
337
|
+
self.current_strategy = self.strategies[BranchStrategyType.ISSUE_DRIVEN]
|
|
338
|
+
return self.current_strategy
|
|
339
|
+
|
|
340
|
+
def set_strategy(self, strategy_type: BranchStrategyType) -> bool:
|
|
341
|
+
"""
|
|
342
|
+
Set the current branch strategy.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
strategy_type: Type of strategy to use
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
True if strategy was set successfully
|
|
349
|
+
"""
|
|
350
|
+
if strategy_type in self.strategies:
|
|
351
|
+
self.current_strategy = self.strategies[strategy_type]
|
|
352
|
+
self.logger.info(f"Set branch strategy to {strategy_type.value}")
|
|
353
|
+
return True
|
|
354
|
+
else:
|
|
355
|
+
self.logger.error(f"Unknown strategy type: {strategy_type}")
|
|
356
|
+
return False
|
|
357
|
+
|
|
358
|
+
def generate_branch_name(
|
|
359
|
+
self,
|
|
360
|
+
branch_type: BranchType,
|
|
361
|
+
ticket_id: Optional[str] = None,
|
|
362
|
+
description: Optional[str] = None,
|
|
363
|
+
**kwargs,
|
|
364
|
+
) -> str:
|
|
365
|
+
"""
|
|
366
|
+
Generate a branch name following the current strategy.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
branch_type: Type of branch to create
|
|
370
|
+
ticket_id: Ticket/issue ID
|
|
371
|
+
description: Optional description
|
|
372
|
+
**kwargs: Additional parameters
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
Generated branch name
|
|
376
|
+
"""
|
|
377
|
+
strategy = self.get_current_strategy()
|
|
378
|
+
|
|
379
|
+
# Find naming rule for this branch type
|
|
380
|
+
naming_rule = None
|
|
381
|
+
for rule in strategy.naming_rules:
|
|
382
|
+
if rule.branch_type == branch_type:
|
|
383
|
+
naming_rule = rule
|
|
384
|
+
break
|
|
385
|
+
|
|
386
|
+
if not naming_rule:
|
|
387
|
+
# Fallback to simple naming
|
|
388
|
+
prefix = branch_type.value + "/"
|
|
389
|
+
if ticket_id:
|
|
390
|
+
return f"{prefix}{ticket_id}"
|
|
391
|
+
elif description:
|
|
392
|
+
return f"{prefix}{self._sanitize_branch_name(description)}"
|
|
393
|
+
else:
|
|
394
|
+
return f"{prefix}{datetime.now().strftime('%Y%m%d')}"
|
|
395
|
+
|
|
396
|
+
# Generate name based on rule
|
|
397
|
+
if strategy.strategy_type == BranchStrategyType.ISSUE_DRIVEN:
|
|
398
|
+
if ticket_id:
|
|
399
|
+
branch_name = f"{naming_rule.prefix}{ticket_id}"
|
|
400
|
+
if description:
|
|
401
|
+
sanitized_desc = self._sanitize_branch_name(description)
|
|
402
|
+
branch_name += f"-{sanitized_desc}"
|
|
403
|
+
return branch_name
|
|
404
|
+
|
|
405
|
+
# Default generation
|
|
406
|
+
return f"{naming_rule.prefix}{ticket_id or 'branch'}"
|
|
407
|
+
|
|
408
|
+
def _sanitize_branch_name(self, name: str) -> str:
|
|
409
|
+
"""Sanitize a string for use in branch names."""
|
|
410
|
+
# Convert to lowercase and replace spaces/special chars with hyphens
|
|
411
|
+
sanitized = re.sub(r"[^a-zA-Z0-9\-_]", "-", name.lower())
|
|
412
|
+
# Remove multiple consecutive hyphens
|
|
413
|
+
sanitized = re.sub(r"-+", "-", sanitized)
|
|
414
|
+
# Remove leading/trailing hyphens
|
|
415
|
+
sanitized = sanitized.strip("-")
|
|
416
|
+
# Limit length
|
|
417
|
+
return sanitized[:50]
|
|
418
|
+
|
|
419
|
+
def validate_branch_name(self, branch_name: str) -> Tuple[bool, str]:
|
|
420
|
+
"""
|
|
421
|
+
Validate a branch name against current strategy rules.
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
branch_name: Branch name to validate
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
Tuple of (is_valid, error_message)
|
|
428
|
+
"""
|
|
429
|
+
strategy = self.get_current_strategy()
|
|
430
|
+
|
|
431
|
+
# Check against naming rules
|
|
432
|
+
for rule in strategy.naming_rules:
|
|
433
|
+
if branch_name.startswith(rule.prefix):
|
|
434
|
+
# Check pattern
|
|
435
|
+
if not re.match(rule.pattern, branch_name):
|
|
436
|
+
return False, f"Branch name doesn't match pattern: {rule.pattern}"
|
|
437
|
+
|
|
438
|
+
# Check length
|
|
439
|
+
if rule.max_length and len(branch_name) > rule.max_length:
|
|
440
|
+
return False, f"Branch name exceeds maximum length: {rule.max_length}"
|
|
441
|
+
|
|
442
|
+
return True, "Valid branch name"
|
|
443
|
+
|
|
444
|
+
# Check if it's a protected branch
|
|
445
|
+
protected_branches = [strategy.main_branch]
|
|
446
|
+
if strategy.development_branch:
|
|
447
|
+
protected_branches.append(strategy.development_branch)
|
|
448
|
+
|
|
449
|
+
if branch_name in protected_branches:
|
|
450
|
+
return False, f"Cannot use protected branch name: {branch_name}"
|
|
451
|
+
|
|
452
|
+
return True, "Valid branch name"
|
|
453
|
+
|
|
454
|
+
def get_merge_target(self, branch_name: str) -> Optional[str]:
|
|
455
|
+
"""
|
|
456
|
+
Get the merge target for a branch based on current strategy.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
branch_name: Source branch name
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
Target branch name or None
|
|
463
|
+
"""
|
|
464
|
+
strategy = self.get_current_strategy()
|
|
465
|
+
|
|
466
|
+
# Check specific merge targets
|
|
467
|
+
for pattern, target in strategy.merge_targets.items():
|
|
468
|
+
if pattern == "*" or branch_name.startswith(pattern.rstrip("*")):
|
|
469
|
+
return target
|
|
470
|
+
|
|
471
|
+
# Default to main branch
|
|
472
|
+
return strategy.main_branch
|
|
473
|
+
|
|
474
|
+
def get_lifecycle_rule(self, branch_name: str) -> Optional[BranchLifecycleRule]:
|
|
475
|
+
"""
|
|
476
|
+
Get the lifecycle rule for a branch.
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
branch_name: Branch name
|
|
480
|
+
|
|
481
|
+
Returns:
|
|
482
|
+
BranchLifecycleRule or None
|
|
483
|
+
"""
|
|
484
|
+
strategy = self.get_current_strategy()
|
|
485
|
+
|
|
486
|
+
# Determine branch type from name
|
|
487
|
+
branch_type = self._get_branch_type(branch_name)
|
|
488
|
+
|
|
489
|
+
# Find matching lifecycle rule
|
|
490
|
+
for rule in strategy.lifecycle_rules:
|
|
491
|
+
if rule.branch_type == branch_type:
|
|
492
|
+
return rule
|
|
493
|
+
|
|
494
|
+
return None
|
|
495
|
+
|
|
496
|
+
def _get_branch_type(self, branch_name: str) -> BranchType:
|
|
497
|
+
"""Determine branch type from branch name."""
|
|
498
|
+
strategy = self.get_current_strategy()
|
|
499
|
+
|
|
500
|
+
# Check against naming rules
|
|
501
|
+
for rule in strategy.naming_rules:
|
|
502
|
+
if branch_name.startswith(rule.prefix):
|
|
503
|
+
return rule.branch_type
|
|
504
|
+
|
|
505
|
+
# Default to feature
|
|
506
|
+
return BranchType.FEATURE
|
|
507
|
+
|
|
508
|
+
def should_auto_merge(self, branch_name: str) -> bool:
|
|
509
|
+
"""
|
|
510
|
+
Check if a branch should be automatically merged.
|
|
511
|
+
|
|
512
|
+
Args:
|
|
513
|
+
branch_name: Branch name
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
True if branch should be auto-merged
|
|
517
|
+
"""
|
|
518
|
+
rule = self.get_lifecycle_rule(branch_name)
|
|
519
|
+
return rule and rule.auto_merge_target is not None
|
|
520
|
+
|
|
521
|
+
def should_delete_after_merge(self, branch_name: str) -> bool:
|
|
522
|
+
"""
|
|
523
|
+
Check if a branch should be deleted after merge.
|
|
524
|
+
|
|
525
|
+
Args:
|
|
526
|
+
branch_name: Branch name
|
|
527
|
+
|
|
528
|
+
Returns:
|
|
529
|
+
True if branch should be deleted after merge
|
|
530
|
+
"""
|
|
531
|
+
rule = self.get_lifecycle_rule(branch_name)
|
|
532
|
+
return rule and rule.auto_delete_after_merge
|
|
533
|
+
|
|
534
|
+
def requires_qa_approval(self, branch_name: str) -> bool:
|
|
535
|
+
"""
|
|
536
|
+
Check if a branch requires QA approval before merge.
|
|
537
|
+
|
|
538
|
+
Args:
|
|
539
|
+
branch_name: Branch name
|
|
540
|
+
|
|
541
|
+
Returns:
|
|
542
|
+
True if QA approval is required
|
|
543
|
+
"""
|
|
544
|
+
rule = self.get_lifecycle_rule(branch_name)
|
|
545
|
+
return rule and rule.requires_qa_approval
|
|
546
|
+
|
|
547
|
+
def requires_code_review(self, branch_name: str) -> bool:
|
|
548
|
+
"""
|
|
549
|
+
Check if a branch requires code review before merge.
|
|
550
|
+
|
|
551
|
+
Args:
|
|
552
|
+
branch_name: Branch name
|
|
553
|
+
|
|
554
|
+
Returns:
|
|
555
|
+
True if code review is required
|
|
556
|
+
"""
|
|
557
|
+
rule = self.get_lifecycle_rule(branch_name)
|
|
558
|
+
return rule and rule.requires_review
|
|
559
|
+
|
|
560
|
+
def get_merge_strategy(self, branch_name: str) -> str:
|
|
561
|
+
"""
|
|
562
|
+
Get the merge strategy for a branch.
|
|
563
|
+
|
|
564
|
+
Args:
|
|
565
|
+
branch_name: Branch name
|
|
566
|
+
|
|
567
|
+
Returns:
|
|
568
|
+
Merge strategy (merge, squash, rebase)
|
|
569
|
+
"""
|
|
570
|
+
rule = self.get_lifecycle_rule(branch_name)
|
|
571
|
+
return rule.auto_merge_strategy if rule else "merge"
|
|
572
|
+
|
|
573
|
+
def generate_merge_message(
|
|
574
|
+
self, branch_name: str, ticket_title: Optional[str] = None, **kwargs
|
|
575
|
+
) -> str:
|
|
576
|
+
"""
|
|
577
|
+
Generate merge commit message based on strategy.
|
|
578
|
+
|
|
579
|
+
Args:
|
|
580
|
+
branch_name: Source branch name
|
|
581
|
+
ticket_title: Optional ticket title
|
|
582
|
+
**kwargs: Additional template variables
|
|
583
|
+
|
|
584
|
+
Returns:
|
|
585
|
+
Generated merge message
|
|
586
|
+
"""
|
|
587
|
+
rule = self.get_lifecycle_rule(branch_name)
|
|
588
|
+
|
|
589
|
+
if rule and rule.merge_message_template:
|
|
590
|
+
template = rule.merge_message_template
|
|
591
|
+
|
|
592
|
+
# Replace template variables
|
|
593
|
+
variables = {
|
|
594
|
+
"branch_name": branch_name,
|
|
595
|
+
"ticket_title": ticket_title or "Updates",
|
|
596
|
+
**kwargs,
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
for key, value in variables.items():
|
|
600
|
+
template = template.replace(f"{{{key}}}", str(value))
|
|
601
|
+
|
|
602
|
+
return template
|
|
603
|
+
|
|
604
|
+
# Default merge message
|
|
605
|
+
if ticket_title:
|
|
606
|
+
return f"Merge {branch_name}: {ticket_title}"
|
|
607
|
+
else:
|
|
608
|
+
return f"Merge {branch_name}"
|
|
609
|
+
|
|
610
|
+
def get_quality_gates(self) -> List[str]:
|
|
611
|
+
"""Get quality gates for the current strategy."""
|
|
612
|
+
strategy = self.get_current_strategy()
|
|
613
|
+
return strategy.quality_gates
|
|
614
|
+
|
|
615
|
+
def create_custom_strategy(self, name: str, config: Dict[str, Any]) -> BranchWorkflow:
|
|
616
|
+
"""
|
|
617
|
+
Create a custom branch strategy.
|
|
618
|
+
|
|
619
|
+
Args:
|
|
620
|
+
name: Strategy name
|
|
621
|
+
config: Strategy configuration
|
|
622
|
+
|
|
623
|
+
Returns:
|
|
624
|
+
Custom BranchWorkflow
|
|
625
|
+
"""
|
|
626
|
+
# This would implement custom strategy creation
|
|
627
|
+
# For now, return a basic custom strategy
|
|
628
|
+
return BranchWorkflow(
|
|
629
|
+
strategy_type=BranchStrategyType.CUSTOM,
|
|
630
|
+
main_branch=config.get("main_branch", "main"),
|
|
631
|
+
naming_rules=[],
|
|
632
|
+
lifecycle_rules=[],
|
|
633
|
+
merge_targets={},
|
|
634
|
+
quality_gates=[],
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
def export_strategy_config(self) -> Dict[str, Any]:
|
|
638
|
+
"""Export current strategy as configuration."""
|
|
639
|
+
strategy = self.get_current_strategy()
|
|
640
|
+
|
|
641
|
+
return {
|
|
642
|
+
"strategy_type": strategy.strategy_type.value,
|
|
643
|
+
"main_branch": strategy.main_branch,
|
|
644
|
+
"development_branch": strategy.development_branch,
|
|
645
|
+
"naming_rules": [
|
|
646
|
+
{
|
|
647
|
+
"branch_type": rule.branch_type.value,
|
|
648
|
+
"prefix": rule.prefix,
|
|
649
|
+
"pattern": rule.pattern,
|
|
650
|
+
"required_fields": rule.required_fields,
|
|
651
|
+
"max_length": rule.max_length,
|
|
652
|
+
"description": rule.description,
|
|
653
|
+
}
|
|
654
|
+
for rule in strategy.naming_rules
|
|
655
|
+
],
|
|
656
|
+
"lifecycle_rules": [
|
|
657
|
+
{
|
|
658
|
+
"branch_type": rule.branch_type.value,
|
|
659
|
+
"auto_merge_target": rule.auto_merge_target,
|
|
660
|
+
"auto_merge_strategy": rule.auto_merge_strategy,
|
|
661
|
+
"auto_delete_after_merge": rule.auto_delete_after_merge,
|
|
662
|
+
"requires_qa_approval": rule.requires_qa_approval,
|
|
663
|
+
"requires_review": rule.requires_review,
|
|
664
|
+
"merge_message_template": rule.merge_message_template,
|
|
665
|
+
}
|
|
666
|
+
for rule in strategy.lifecycle_rules
|
|
667
|
+
],
|
|
668
|
+
"merge_targets": strategy.merge_targets,
|
|
669
|
+
"quality_gates": strategy.quality_gates,
|
|
670
|
+
}
|