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,703 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Semantic Versioning Manager - Version management logic for Version Control Agent.
|
|
3
|
+
|
|
4
|
+
This module provides comprehensive semantic versioning management including:
|
|
5
|
+
1. Version parsing and validation
|
|
6
|
+
2. Automatic version bumping based on changes
|
|
7
|
+
3. Changelog generation and management
|
|
8
|
+
4. Tag creation and management
|
|
9
|
+
5. Version metadata handling
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
import json
|
|
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
|
+
from ...utils.config_manager import ConfigurationManager
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class VersionBumpType(Enum):
|
|
25
|
+
"""Types of version bumps."""
|
|
26
|
+
|
|
27
|
+
MAJOR = "major"
|
|
28
|
+
MINOR = "minor"
|
|
29
|
+
PATCH = "patch"
|
|
30
|
+
PRERELEASE = "prerelease"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class SemanticVersion:
|
|
35
|
+
"""Represents a semantic version."""
|
|
36
|
+
|
|
37
|
+
major: int
|
|
38
|
+
minor: int
|
|
39
|
+
patch: int
|
|
40
|
+
prerelease: Optional[str] = None
|
|
41
|
+
build: Optional[str] = None
|
|
42
|
+
|
|
43
|
+
def __str__(self) -> str:
|
|
44
|
+
"""String representation of version."""
|
|
45
|
+
version = f"{self.major}.{self.minor}.{self.patch}"
|
|
46
|
+
if self.prerelease:
|
|
47
|
+
version += f"-{self.prerelease}"
|
|
48
|
+
if self.build:
|
|
49
|
+
version += f"+{self.build}"
|
|
50
|
+
return version
|
|
51
|
+
|
|
52
|
+
def __lt__(self, other: "SemanticVersion") -> bool:
|
|
53
|
+
"""Compare versions for sorting."""
|
|
54
|
+
if self.major != other.major:
|
|
55
|
+
return self.major < other.major
|
|
56
|
+
if self.minor != other.minor:
|
|
57
|
+
return self.minor < other.minor
|
|
58
|
+
if self.patch != other.patch:
|
|
59
|
+
return self.patch < other.patch
|
|
60
|
+
|
|
61
|
+
# Handle prerelease comparison
|
|
62
|
+
if self.prerelease is None and other.prerelease is not None:
|
|
63
|
+
return False
|
|
64
|
+
if self.prerelease is not None and other.prerelease is None:
|
|
65
|
+
return True
|
|
66
|
+
if self.prerelease is not None and other.prerelease is not None:
|
|
67
|
+
return self.prerelease < other.prerelease
|
|
68
|
+
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
def bump(self, bump_type: VersionBumpType) -> "SemanticVersion":
|
|
72
|
+
"""Create a new version with the specified bump applied."""
|
|
73
|
+
if bump_type == VersionBumpType.MAJOR:
|
|
74
|
+
return SemanticVersion(self.major + 1, 0, 0)
|
|
75
|
+
elif bump_type == VersionBumpType.MINOR:
|
|
76
|
+
return SemanticVersion(self.major, self.minor + 1, 0)
|
|
77
|
+
elif bump_type == VersionBumpType.PATCH:
|
|
78
|
+
return SemanticVersion(self.major, self.minor, self.patch + 1)
|
|
79
|
+
elif bump_type == VersionBumpType.PRERELEASE:
|
|
80
|
+
if self.prerelease:
|
|
81
|
+
# Increment prerelease number
|
|
82
|
+
match = re.match(r"(.+?)(\d+)$", self.prerelease)
|
|
83
|
+
if match:
|
|
84
|
+
prefix, num = match.groups()
|
|
85
|
+
new_prerelease = f"{prefix}{int(num) + 1}"
|
|
86
|
+
else:
|
|
87
|
+
new_prerelease = f"{self.prerelease}.1"
|
|
88
|
+
else:
|
|
89
|
+
new_prerelease = "alpha.1"
|
|
90
|
+
|
|
91
|
+
return SemanticVersion(self.major, self.minor, self.patch, prerelease=new_prerelease)
|
|
92
|
+
|
|
93
|
+
return self
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class VersionMetadata:
|
|
98
|
+
"""Metadata associated with a version."""
|
|
99
|
+
|
|
100
|
+
version: SemanticVersion
|
|
101
|
+
release_date: datetime
|
|
102
|
+
commit_hash: Optional[str] = None
|
|
103
|
+
tag_name: Optional[str] = None
|
|
104
|
+
changes: List[str] = field(default_factory=list)
|
|
105
|
+
breaking_changes: List[str] = field(default_factory=list)
|
|
106
|
+
contributors: List[str] = field(default_factory=list)
|
|
107
|
+
notes: Optional[str] = None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass
|
|
111
|
+
class ChangeAnalysis:
|
|
112
|
+
"""Analysis of changes for version bumping."""
|
|
113
|
+
|
|
114
|
+
has_breaking_changes: bool = False
|
|
115
|
+
has_new_features: bool = False
|
|
116
|
+
has_bug_fixes: bool = False
|
|
117
|
+
change_descriptions: List[str] = field(default_factory=list)
|
|
118
|
+
suggested_bump: VersionBumpType = VersionBumpType.PATCH
|
|
119
|
+
confidence: float = 0.0
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class SemanticVersionManager:
|
|
123
|
+
"""
|
|
124
|
+
Manages semantic versioning for the Version Control Agent.
|
|
125
|
+
|
|
126
|
+
Provides comprehensive version management including parsing, bumping,
|
|
127
|
+
changelog generation, and integration with Git tags.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
def __init__(self, project_root: str, logger: logging.Logger):
|
|
131
|
+
"""
|
|
132
|
+
Initialize Semantic Version Manager.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
project_root: Root directory of the project
|
|
136
|
+
logger: Logger instance
|
|
137
|
+
"""
|
|
138
|
+
self.project_root = Path(project_root)
|
|
139
|
+
self.logger = logger
|
|
140
|
+
self.config_mgr = ConfigurationManager(cache_enabled=True)
|
|
141
|
+
|
|
142
|
+
# Version file patterns
|
|
143
|
+
self.version_files = {
|
|
144
|
+
"package.json": self._parse_package_json_version,
|
|
145
|
+
"pyproject.toml": self._parse_pyproject_toml_version,
|
|
146
|
+
"Cargo.toml": self._parse_cargo_toml_version,
|
|
147
|
+
"VERSION": self._parse_version_file,
|
|
148
|
+
"version.txt": self._parse_version_file,
|
|
149
|
+
"pom.xml": self._parse_pom_xml_version,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
# Change patterns for analysis
|
|
153
|
+
self.breaking_change_patterns = [
|
|
154
|
+
r"\bbreaking\b",
|
|
155
|
+
r"\bbreaking[-_]change\b",
|
|
156
|
+
r"\bremove\b.*\bapi\b",
|
|
157
|
+
r"\bdelete\b.*\bapi\b",
|
|
158
|
+
r"\bdrop\b.*\bsupport\b",
|
|
159
|
+
r"\bincompatible\b",
|
|
160
|
+
r"\bmajor\b.*\bchange\b",
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
self.feature_patterns = [
|
|
164
|
+
r"\badd\b",
|
|
165
|
+
r"\bnew\b.*\bfeature\b",
|
|
166
|
+
r"\bimplement\b",
|
|
167
|
+
r"\benhance\b",
|
|
168
|
+
r"\bintroduce\b",
|
|
169
|
+
r"\bfeature\b.*\badd\b",
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
self.bug_fix_patterns = [
|
|
173
|
+
r"\bfix\b",
|
|
174
|
+
r"\bbug\b.*\bfix\b",
|
|
175
|
+
r"\bresolve\b",
|
|
176
|
+
r"\bcorrect\b",
|
|
177
|
+
r"\bpatch\b",
|
|
178
|
+
r"\bhotfix\b",
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
def parse_version(self, version_string: str) -> Optional[SemanticVersion]:
|
|
182
|
+
"""
|
|
183
|
+
Parse a version string into a SemanticVersion object.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
version_string: Version string to parse
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
SemanticVersion object or None if parsing fails
|
|
190
|
+
"""
|
|
191
|
+
try:
|
|
192
|
+
# Clean up version string
|
|
193
|
+
version_string = version_string.strip().lstrip("v")
|
|
194
|
+
|
|
195
|
+
# Regex pattern for semantic version
|
|
196
|
+
pattern = r"^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9\-\.]+))?(?:\+([a-zA-Z0-9\-\.]+))?$"
|
|
197
|
+
match = re.match(pattern, version_string)
|
|
198
|
+
|
|
199
|
+
if match:
|
|
200
|
+
major, minor, patch, prerelease, build = match.groups()
|
|
201
|
+
|
|
202
|
+
return SemanticVersion(
|
|
203
|
+
major=int(major),
|
|
204
|
+
minor=int(minor),
|
|
205
|
+
patch=int(patch),
|
|
206
|
+
prerelease=prerelease,
|
|
207
|
+
build=build,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return None
|
|
211
|
+
|
|
212
|
+
except Exception as e:
|
|
213
|
+
self.logger.error(f"Error parsing version '{version_string}': {e}")
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
def get_current_version(self) -> Optional[SemanticVersion]:
|
|
217
|
+
"""
|
|
218
|
+
Get the current version from project files.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
Current SemanticVersion or None if not found
|
|
222
|
+
"""
|
|
223
|
+
for filename, parser in self.version_files.items():
|
|
224
|
+
file_path = self.project_root / filename
|
|
225
|
+
|
|
226
|
+
if file_path.exists():
|
|
227
|
+
try:
|
|
228
|
+
version_string = parser(file_path)
|
|
229
|
+
if version_string:
|
|
230
|
+
version = self.parse_version(version_string)
|
|
231
|
+
if version:
|
|
232
|
+
self.logger.info(f"Found version {version} in {filename}")
|
|
233
|
+
return version
|
|
234
|
+
except Exception as e:
|
|
235
|
+
self.logger.error(f"Error parsing version from {filename}: {e}")
|
|
236
|
+
continue
|
|
237
|
+
|
|
238
|
+
self.logger.warning("No version found in project files")
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
def _parse_package_json_version(self, file_path: Path) -> Optional[str]:
|
|
242
|
+
"""Parse version from package.json."""
|
|
243
|
+
try:
|
|
244
|
+
data = self.config_mgr.load_json(file_path)
|
|
245
|
+
return data.get("version")
|
|
246
|
+
except Exception:
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
def _parse_pyproject_toml_version(self, file_path: Path) -> Optional[str]:
|
|
250
|
+
"""Parse version from pyproject.toml."""
|
|
251
|
+
try:
|
|
252
|
+
import tomllib
|
|
253
|
+
except ImportError:
|
|
254
|
+
try:
|
|
255
|
+
import tomli as tomllib
|
|
256
|
+
except ImportError:
|
|
257
|
+
# Fallback to simple regex parsing
|
|
258
|
+
return self._parse_toml_version_regex(file_path)
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
with open(file_path, "rb") as f:
|
|
262
|
+
data = tomllib.load(f)
|
|
263
|
+
|
|
264
|
+
# Try different locations for version
|
|
265
|
+
if "project" in data and "version" in data["project"]:
|
|
266
|
+
return data["project"]["version"]
|
|
267
|
+
elif (
|
|
268
|
+
"tool" in data and "poetry" in data["tool"] and "version" in data["tool"]["poetry"]
|
|
269
|
+
):
|
|
270
|
+
return data["tool"]["poetry"]["version"]
|
|
271
|
+
|
|
272
|
+
return None
|
|
273
|
+
|
|
274
|
+
except Exception:
|
|
275
|
+
return self._parse_toml_version_regex(file_path)
|
|
276
|
+
|
|
277
|
+
def _parse_toml_version_regex(self, file_path: Path) -> Optional[str]:
|
|
278
|
+
"""Parse version from TOML file using regex."""
|
|
279
|
+
try:
|
|
280
|
+
with open(file_path, "r") as f:
|
|
281
|
+
content = f.read()
|
|
282
|
+
|
|
283
|
+
# Look for version = "x.y.z" pattern
|
|
284
|
+
patterns = [r'version\s*=\s*["\']([^"\']+)["\']', r'version:\s*["\']([^"\']+)["\']']
|
|
285
|
+
|
|
286
|
+
for pattern in patterns:
|
|
287
|
+
match = re.search(pattern, content)
|
|
288
|
+
if match:
|
|
289
|
+
return match.group(1)
|
|
290
|
+
|
|
291
|
+
return None
|
|
292
|
+
|
|
293
|
+
except Exception:
|
|
294
|
+
return None
|
|
295
|
+
|
|
296
|
+
def _parse_cargo_toml_version(self, file_path: Path) -> Optional[str]:
|
|
297
|
+
"""Parse version from Cargo.toml."""
|
|
298
|
+
return self._parse_toml_version_regex(file_path)
|
|
299
|
+
|
|
300
|
+
def _parse_version_file(self, file_path: Path) -> Optional[str]:
|
|
301
|
+
"""Parse version from simple version file."""
|
|
302
|
+
try:
|
|
303
|
+
with open(file_path, "r") as f:
|
|
304
|
+
return f.read().strip()
|
|
305
|
+
except Exception:
|
|
306
|
+
return None
|
|
307
|
+
|
|
308
|
+
def _parse_pom_xml_version(self, file_path: Path) -> Optional[str]:
|
|
309
|
+
"""Parse version from Maven pom.xml."""
|
|
310
|
+
try:
|
|
311
|
+
with open(file_path, "r") as f:
|
|
312
|
+
content = f.read()
|
|
313
|
+
|
|
314
|
+
# Simple regex to find version in pom.xml
|
|
315
|
+
pattern = r"<version>([^<]+)</version>"
|
|
316
|
+
match = re.search(pattern, content)
|
|
317
|
+
|
|
318
|
+
if match:
|
|
319
|
+
return match.group(1)
|
|
320
|
+
|
|
321
|
+
return None
|
|
322
|
+
|
|
323
|
+
except Exception:
|
|
324
|
+
return None
|
|
325
|
+
|
|
326
|
+
def analyze_changes(self, changes: List[str]) -> ChangeAnalysis:
|
|
327
|
+
"""
|
|
328
|
+
Analyze changes to suggest version bump type.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
changes: List of change descriptions (e.g., commit messages)
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
ChangeAnalysis with suggested version bump
|
|
335
|
+
"""
|
|
336
|
+
analysis = ChangeAnalysis()
|
|
337
|
+
analysis.change_descriptions = changes
|
|
338
|
+
|
|
339
|
+
# Analyze each change
|
|
340
|
+
for change in changes:
|
|
341
|
+
change_lower = change.lower()
|
|
342
|
+
|
|
343
|
+
# Check for breaking changes
|
|
344
|
+
if any(re.search(pattern, change_lower) for pattern in self.breaking_change_patterns):
|
|
345
|
+
analysis.has_breaking_changes = True
|
|
346
|
+
|
|
347
|
+
# Check for new features
|
|
348
|
+
elif any(re.search(pattern, change_lower) for pattern in self.feature_patterns):
|
|
349
|
+
analysis.has_new_features = True
|
|
350
|
+
|
|
351
|
+
# Check for bug fixes
|
|
352
|
+
elif any(re.search(pattern, change_lower) for pattern in self.bug_fix_patterns):
|
|
353
|
+
analysis.has_bug_fixes = True
|
|
354
|
+
|
|
355
|
+
# Determine suggested bump
|
|
356
|
+
if analysis.has_breaking_changes:
|
|
357
|
+
analysis.suggested_bump = VersionBumpType.MAJOR
|
|
358
|
+
analysis.confidence = 0.9
|
|
359
|
+
elif analysis.has_new_features:
|
|
360
|
+
analysis.suggested_bump = VersionBumpType.MINOR
|
|
361
|
+
analysis.confidence = 0.8
|
|
362
|
+
elif analysis.has_bug_fixes:
|
|
363
|
+
analysis.suggested_bump = VersionBumpType.PATCH
|
|
364
|
+
analysis.confidence = 0.7
|
|
365
|
+
else:
|
|
366
|
+
analysis.suggested_bump = VersionBumpType.PATCH
|
|
367
|
+
analysis.confidence = 0.5
|
|
368
|
+
|
|
369
|
+
return analysis
|
|
370
|
+
|
|
371
|
+
def bump_version(
|
|
372
|
+
self, current_version: SemanticVersion, bump_type: VersionBumpType
|
|
373
|
+
) -> SemanticVersion:
|
|
374
|
+
"""
|
|
375
|
+
Bump version according to semantic versioning rules.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
current_version: Current version
|
|
379
|
+
bump_type: Type of bump to apply
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
New version
|
|
383
|
+
"""
|
|
384
|
+
return current_version.bump(bump_type)
|
|
385
|
+
|
|
386
|
+
def suggest_version_bump(self, commit_messages: List[str]) -> Tuple[VersionBumpType, float]:
|
|
387
|
+
"""
|
|
388
|
+
Suggest version bump based on commit messages.
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
commit_messages: List of commit messages since last version
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
Tuple of (suggested_bump_type, confidence_score)
|
|
395
|
+
"""
|
|
396
|
+
analysis = self.analyze_changes(commit_messages)
|
|
397
|
+
return analysis.suggested_bump, analysis.confidence
|
|
398
|
+
|
|
399
|
+
def update_version_files(
|
|
400
|
+
self, new_version: SemanticVersion, files_to_update: Optional[List[str]] = None
|
|
401
|
+
) -> Dict[str, bool]:
|
|
402
|
+
"""
|
|
403
|
+
Update version in project files.
|
|
404
|
+
|
|
405
|
+
Args:
|
|
406
|
+
new_version: New version to set
|
|
407
|
+
files_to_update: Specific files to update (defaults to all found)
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
Dictionary mapping filenames to success status
|
|
411
|
+
"""
|
|
412
|
+
results = {}
|
|
413
|
+
version_string = str(new_version)
|
|
414
|
+
|
|
415
|
+
files_to_check = files_to_update or list(self.version_files.keys())
|
|
416
|
+
|
|
417
|
+
for filename in files_to_check:
|
|
418
|
+
file_path = self.project_root / filename
|
|
419
|
+
|
|
420
|
+
if file_path.exists():
|
|
421
|
+
try:
|
|
422
|
+
success = self._update_version_file(file_path, version_string)
|
|
423
|
+
results[filename] = success
|
|
424
|
+
|
|
425
|
+
if success:
|
|
426
|
+
self.logger.info(f"Updated version to {version_string} in {filename}")
|
|
427
|
+
else:
|
|
428
|
+
self.logger.error(f"Failed to update version in {filename}")
|
|
429
|
+
|
|
430
|
+
except Exception as e:
|
|
431
|
+
self.logger.error(f"Error updating version in {filename}: {e}")
|
|
432
|
+
results[filename] = False
|
|
433
|
+
|
|
434
|
+
return results
|
|
435
|
+
|
|
436
|
+
def _update_version_file(self, file_path: Path, new_version: str) -> bool:
|
|
437
|
+
"""Update version in a specific file."""
|
|
438
|
+
filename = file_path.name
|
|
439
|
+
|
|
440
|
+
try:
|
|
441
|
+
if filename == "package.json":
|
|
442
|
+
return self._update_package_json_version(file_path, new_version)
|
|
443
|
+
elif filename in ["pyproject.toml", "Cargo.toml"]:
|
|
444
|
+
return self._update_toml_version(file_path, new_version)
|
|
445
|
+
elif filename in ["VERSION", "version.txt"]:
|
|
446
|
+
return self._update_simple_version_file(file_path, new_version)
|
|
447
|
+
elif filename == "pom.xml":
|
|
448
|
+
return self._update_pom_xml_version(file_path, new_version)
|
|
449
|
+
|
|
450
|
+
return False
|
|
451
|
+
|
|
452
|
+
except Exception as e:
|
|
453
|
+
self.logger.error(f"Error updating {filename}: {e}")
|
|
454
|
+
return False
|
|
455
|
+
|
|
456
|
+
def _update_package_json_version(self, file_path: Path, new_version: str) -> bool:
|
|
457
|
+
"""Update version in package.json."""
|
|
458
|
+
try:
|
|
459
|
+
data = self.config_mgr.load_json(file_path)
|
|
460
|
+
data["version"] = new_version
|
|
461
|
+
|
|
462
|
+
self.config_mgr.save_json(data, file_path)
|
|
463
|
+
|
|
464
|
+
return True
|
|
465
|
+
|
|
466
|
+
except Exception:
|
|
467
|
+
return False
|
|
468
|
+
|
|
469
|
+
def _update_toml_version(self, file_path: Path, new_version: str) -> bool:
|
|
470
|
+
"""Update version in TOML file."""
|
|
471
|
+
try:
|
|
472
|
+
with open(file_path, "r") as f:
|
|
473
|
+
content = f.read()
|
|
474
|
+
|
|
475
|
+
# Replace version field
|
|
476
|
+
patterns = [
|
|
477
|
+
(r'(version\s*=\s*)["\']([^"\']+)["\']', rf'\g<1>"{new_version}"'),
|
|
478
|
+
(r'(version:\s*)["\']([^"\']+)["\']', rf'\g<1>"{new_version}"'),
|
|
479
|
+
]
|
|
480
|
+
|
|
481
|
+
updated = False
|
|
482
|
+
for pattern, replacement in patterns:
|
|
483
|
+
new_content = re.sub(pattern, replacement, content)
|
|
484
|
+
if new_content != content:
|
|
485
|
+
content = new_content
|
|
486
|
+
updated = True
|
|
487
|
+
break
|
|
488
|
+
|
|
489
|
+
if updated:
|
|
490
|
+
with open(file_path, "w") as f:
|
|
491
|
+
f.write(content)
|
|
492
|
+
return True
|
|
493
|
+
|
|
494
|
+
return False
|
|
495
|
+
|
|
496
|
+
except Exception:
|
|
497
|
+
return False
|
|
498
|
+
|
|
499
|
+
def _update_simple_version_file(self, file_path: Path, new_version: str) -> bool:
|
|
500
|
+
"""Update version in simple version file."""
|
|
501
|
+
try:
|
|
502
|
+
with open(file_path, "w") as f:
|
|
503
|
+
f.write(new_version + "\n")
|
|
504
|
+
return True
|
|
505
|
+
except Exception:
|
|
506
|
+
return False
|
|
507
|
+
|
|
508
|
+
def _update_pom_xml_version(self, file_path: Path, new_version: str) -> bool:
|
|
509
|
+
"""Update version in Maven pom.xml."""
|
|
510
|
+
try:
|
|
511
|
+
with open(file_path, "r") as f:
|
|
512
|
+
content = f.read()
|
|
513
|
+
|
|
514
|
+
# Replace first version tag (project version)
|
|
515
|
+
pattern = r"(<version>)[^<]+(</version>)"
|
|
516
|
+
replacement = rf"\g<1>{new_version}\g<2>"
|
|
517
|
+
|
|
518
|
+
new_content = re.sub(pattern, replacement, content, count=1)
|
|
519
|
+
|
|
520
|
+
if new_content != content:
|
|
521
|
+
with open(file_path, "w") as f:
|
|
522
|
+
f.write(new_content)
|
|
523
|
+
return True
|
|
524
|
+
|
|
525
|
+
return False
|
|
526
|
+
|
|
527
|
+
except Exception:
|
|
528
|
+
return False
|
|
529
|
+
|
|
530
|
+
def generate_changelog_entry(
|
|
531
|
+
self,
|
|
532
|
+
version: SemanticVersion,
|
|
533
|
+
changes: List[str],
|
|
534
|
+
metadata: Optional[VersionMetadata] = None,
|
|
535
|
+
) -> str:
|
|
536
|
+
"""
|
|
537
|
+
Generate changelog entry for a version.
|
|
538
|
+
|
|
539
|
+
Args:
|
|
540
|
+
version: Version for the changelog entry
|
|
541
|
+
changes: List of changes
|
|
542
|
+
metadata: Optional version metadata
|
|
543
|
+
|
|
544
|
+
Returns:
|
|
545
|
+
Formatted changelog entry
|
|
546
|
+
"""
|
|
547
|
+
date_str = datetime.now().strftime("%Y-%m-%d")
|
|
548
|
+
if metadata and metadata.release_date:
|
|
549
|
+
date_str = metadata.release_date.strftime("%Y-%m-%d")
|
|
550
|
+
|
|
551
|
+
# Build changelog entry
|
|
552
|
+
lines = [f"## [{version}] - {date_str}", ""]
|
|
553
|
+
|
|
554
|
+
# Categorize changes
|
|
555
|
+
breaking_changes = []
|
|
556
|
+
features = []
|
|
557
|
+
fixes = []
|
|
558
|
+
other_changes = []
|
|
559
|
+
|
|
560
|
+
for change in changes:
|
|
561
|
+
change_lower = change.lower()
|
|
562
|
+
|
|
563
|
+
if any(re.search(pattern, change_lower) for pattern in self.breaking_change_patterns):
|
|
564
|
+
breaking_changes.append(change)
|
|
565
|
+
elif any(re.search(pattern, change_lower) for pattern in self.feature_patterns):
|
|
566
|
+
features.append(change)
|
|
567
|
+
elif any(re.search(pattern, change_lower) for pattern in self.bug_fix_patterns):
|
|
568
|
+
fixes.append(change)
|
|
569
|
+
else:
|
|
570
|
+
other_changes.append(change)
|
|
571
|
+
|
|
572
|
+
# Add sections
|
|
573
|
+
if breaking_changes:
|
|
574
|
+
lines.extend(["### ⚠ BREAKING CHANGES", ""])
|
|
575
|
+
for change in breaking_changes:
|
|
576
|
+
lines.append(f"- {change}")
|
|
577
|
+
lines.append("")
|
|
578
|
+
|
|
579
|
+
if features:
|
|
580
|
+
lines.extend(["### ✨ Features", ""])
|
|
581
|
+
for change in features:
|
|
582
|
+
lines.append(f"- {change}")
|
|
583
|
+
lines.append("")
|
|
584
|
+
|
|
585
|
+
if fixes:
|
|
586
|
+
lines.extend(["### 🐛 Bug Fixes", ""])
|
|
587
|
+
for change in fixes:
|
|
588
|
+
lines.append(f"- {change}")
|
|
589
|
+
lines.append("")
|
|
590
|
+
|
|
591
|
+
if other_changes:
|
|
592
|
+
lines.extend(["### 📝 Other Changes", ""])
|
|
593
|
+
for change in other_changes:
|
|
594
|
+
lines.append(f"- {change}")
|
|
595
|
+
lines.append("")
|
|
596
|
+
|
|
597
|
+
# Add metadata
|
|
598
|
+
if metadata:
|
|
599
|
+
if metadata.commit_hash:
|
|
600
|
+
lines.append(f"**Commit:** {metadata.commit_hash}")
|
|
601
|
+
if metadata.contributors:
|
|
602
|
+
lines.append(f"**Contributors:** {', '.join(metadata.contributors)}")
|
|
603
|
+
if metadata.notes:
|
|
604
|
+
lines.extend(["", metadata.notes])
|
|
605
|
+
|
|
606
|
+
return "\n".join(lines)
|
|
607
|
+
|
|
608
|
+
def update_changelog(
|
|
609
|
+
self, version: SemanticVersion, changes: List[str], changelog_file: str = "docs/CHANGELOG.md"
|
|
610
|
+
) -> bool:
|
|
611
|
+
"""
|
|
612
|
+
Update CHANGELOG.md with new version entry.
|
|
613
|
+
|
|
614
|
+
Args:
|
|
615
|
+
version: Version for the changelog entry
|
|
616
|
+
changes: List of changes
|
|
617
|
+
changelog_file: Changelog file name
|
|
618
|
+
|
|
619
|
+
Returns:
|
|
620
|
+
True if update was successful
|
|
621
|
+
"""
|
|
622
|
+
changelog_path = self.project_root / changelog_file
|
|
623
|
+
|
|
624
|
+
try:
|
|
625
|
+
# Generate new entry
|
|
626
|
+
new_entry = self.generate_changelog_entry(version, changes)
|
|
627
|
+
|
|
628
|
+
# Read existing changelog or create new one
|
|
629
|
+
if changelog_path.exists():
|
|
630
|
+
with open(changelog_path, "r") as f:
|
|
631
|
+
existing_content = f.read()
|
|
632
|
+
|
|
633
|
+
# Insert new entry after title
|
|
634
|
+
lines = existing_content.split("\n")
|
|
635
|
+
insert_index = 0
|
|
636
|
+
|
|
637
|
+
# Find insertion point (after # Changelog title)
|
|
638
|
+
for i, line in enumerate(lines):
|
|
639
|
+
if line.startswith("# ") or line.startswith("## [Unreleased]"):
|
|
640
|
+
insert_index = i + 1
|
|
641
|
+
break
|
|
642
|
+
|
|
643
|
+
# Insert new entry
|
|
644
|
+
lines.insert(insert_index, new_entry)
|
|
645
|
+
lines.insert(insert_index + 1, "")
|
|
646
|
+
|
|
647
|
+
content = "\n".join(lines)
|
|
648
|
+
else:
|
|
649
|
+
# Create new changelog
|
|
650
|
+
content = f"# Changelog\n\n{new_entry}\n"
|
|
651
|
+
|
|
652
|
+
# Write updated changelog
|
|
653
|
+
with open(changelog_path, "w") as f:
|
|
654
|
+
f.write(content)
|
|
655
|
+
|
|
656
|
+
self.logger.info(f"Updated {changelog_file} with version {version}")
|
|
657
|
+
return True
|
|
658
|
+
|
|
659
|
+
except Exception as e:
|
|
660
|
+
self.logger.error(f"Error updating changelog: {e}")
|
|
661
|
+
return False
|
|
662
|
+
|
|
663
|
+
def get_version_history(self) -> List[SemanticVersion]:
|
|
664
|
+
"""
|
|
665
|
+
Get version history from changelog or Git tags.
|
|
666
|
+
|
|
667
|
+
Returns:
|
|
668
|
+
List of versions in descending order
|
|
669
|
+
"""
|
|
670
|
+
versions = []
|
|
671
|
+
|
|
672
|
+
# Try to get versions from changelog
|
|
673
|
+
changelog_path = self.project_root / "docs" / "CHANGELOG.md"
|
|
674
|
+
if changelog_path.exists():
|
|
675
|
+
versions.extend(self._parse_changelog_versions(changelog_path))
|
|
676
|
+
|
|
677
|
+
# TODO: Add Git tag parsing if needed
|
|
678
|
+
|
|
679
|
+
# Sort versions in descending order
|
|
680
|
+
versions.sort(reverse=True)
|
|
681
|
+
return versions
|
|
682
|
+
|
|
683
|
+
def _parse_changelog_versions(self, changelog_path: Path) -> List[SemanticVersion]:
|
|
684
|
+
"""Parse versions from changelog file."""
|
|
685
|
+
versions = []
|
|
686
|
+
|
|
687
|
+
try:
|
|
688
|
+
with open(changelog_path, "r") as f:
|
|
689
|
+
content = f.read()
|
|
690
|
+
|
|
691
|
+
# Find version entries
|
|
692
|
+
pattern = r"##\s*\[([^\]]+)\]"
|
|
693
|
+
matches = re.findall(pattern, content)
|
|
694
|
+
|
|
695
|
+
for match in matches:
|
|
696
|
+
version = self.parse_version(match)
|
|
697
|
+
if version:
|
|
698
|
+
versions.append(version)
|
|
699
|
+
|
|
700
|
+
except Exception as e:
|
|
701
|
+
self.logger.error(f"Error parsing changelog versions: {e}")
|
|
702
|
+
|
|
703
|
+
return versions
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Terminal UI components for claude-mpm."""
|