claude-mpm 3.4.26__py3-none-any.whl → 3.5.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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/INSTRUCTIONS.md +182 -299
- claude_mpm/agents/agent_loader.py +283 -57
- claude_mpm/agents/agent_loader_integration.py +6 -9
- claude_mpm/agents/base_agent.json +2 -1
- claude_mpm/agents/base_agent_loader.py +1 -1
- claude_mpm/cli/__init__.py +6 -10
- claude_mpm/cli/commands/__init__.py +0 -2
- claude_mpm/cli/commands/agents.py +1 -1
- claude_mpm/cli/commands/memory.py +1 -1
- claude_mpm/cli/commands/run.py +12 -0
- claude_mpm/cli/parser.py +0 -13
- claude_mpm/cli/utils.py +1 -1
- claude_mpm/config/__init__.py +44 -2
- claude_mpm/config/agent_config.py +348 -0
- claude_mpm/config/paths.py +322 -0
- claude_mpm/constants.py +0 -1
- claude_mpm/core/__init__.py +2 -5
- claude_mpm/core/agent_registry.py +63 -17
- claude_mpm/core/claude_runner.py +354 -43
- claude_mpm/core/config.py +7 -1
- claude_mpm/core/config_aliases.py +4 -3
- claude_mpm/core/config_paths.py +151 -0
- claude_mpm/core/factories.py +4 -50
- claude_mpm/core/logger.py +11 -13
- claude_mpm/core/service_registry.py +2 -2
- claude_mpm/dashboard/static/js/components/agent-inference.js +101 -25
- claude_mpm/dashboard/static/js/components/event-processor.js +3 -2
- claude_mpm/hooks/claude_hooks/hook_handler.py +343 -83
- claude_mpm/hooks/memory_integration_hook.py +1 -1
- claude_mpm/init.py +37 -6
- claude_mpm/scripts/socketio_daemon.py +6 -2
- claude_mpm/services/__init__.py +71 -3
- claude_mpm/services/agents/__init__.py +85 -0
- claude_mpm/services/agents/deployment/__init__.py +21 -0
- claude_mpm/services/{agent_deployment.py → agents/deployment/agent_deployment.py} +192 -41
- claude_mpm/services/{agent_lifecycle_manager.py → agents/deployment/agent_lifecycle_manager.py} +11 -10
- claude_mpm/services/agents/loading/__init__.py +11 -0
- claude_mpm/services/{agent_profile_loader.py → agents/loading/agent_profile_loader.py} +9 -8
- claude_mpm/services/{base_agent_manager.py → agents/loading/base_agent_manager.py} +2 -2
- claude_mpm/services/{framework_agent_loader.py → agents/loading/framework_agent_loader.py} +116 -40
- claude_mpm/services/agents/management/__init__.py +9 -0
- claude_mpm/services/{agent_management_service.py → agents/management/agent_management_service.py} +6 -5
- claude_mpm/services/agents/memory/__init__.py +21 -0
- claude_mpm/services/{agent_memory_manager.py → agents/memory/agent_memory_manager.py} +3 -3
- claude_mpm/services/agents/registry/__init__.py +29 -0
- claude_mpm/services/{agent_registry.py → agents/registry/agent_registry.py} +101 -16
- claude_mpm/services/{deployed_agent_discovery.py → agents/registry/deployed_agent_discovery.py} +12 -2
- claude_mpm/services/{agent_modification_tracker.py → agents/registry/modification_tracker.py} +6 -5
- claude_mpm/services/async_session_logger.py +584 -0
- claude_mpm/services/claude_session_logger.py +299 -0
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +2 -2
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +17 -17
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +3 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +1 -1
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +1 -1
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +19 -24
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +1 -1
- claude_mpm/services/framework_claude_md_generator.py +4 -2
- claude_mpm/services/memory/__init__.py +17 -0
- claude_mpm/services/{memory_builder.py → memory/builder.py} +3 -3
- claude_mpm/services/memory/cache/__init__.py +14 -0
- claude_mpm/services/{shared_prompt_cache.py → memory/cache/shared_prompt_cache.py} +1 -1
- claude_mpm/services/memory/cache/simple_cache.py +317 -0
- claude_mpm/services/{memory_optimizer.py → memory/optimizer.py} +1 -1
- claude_mpm/services/{memory_router.py → memory/router.py} +1 -1
- claude_mpm/services/optimized_hook_service.py +542 -0
- claude_mpm/services/project_registry.py +14 -8
- claude_mpm/services/response_tracker.py +237 -0
- claude_mpm/services/ticketing_service_original.py +4 -2
- claude_mpm/services/version_control/branch_strategy.py +3 -1
- claude_mpm/utils/paths.py +12 -10
- claude_mpm/utils/session_logging.py +114 -0
- claude_mpm/validation/agent_validator.py +2 -1
- {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.dist-info}/METADATA +26 -20
- {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.dist-info}/RECORD +83 -106
- claude_mpm/cli/commands/ui.py +0 -57
- claude_mpm/core/simple_runner.py +0 -1046
- claude_mpm/hooks/builtin/__init__.py +0 -1
- claude_mpm/hooks/builtin/logging_hook_example.py +0 -165
- claude_mpm/hooks/builtin/memory_hooks_example.py +0 -67
- claude_mpm/hooks/builtin/mpm_command_hook.py +0 -125
- claude_mpm/hooks/builtin/post_delegation_hook_example.py +0 -124
- claude_mpm/hooks/builtin/pre_delegation_hook_example.py +0 -125
- claude_mpm/hooks/builtin/submit_hook_example.py +0 -100
- claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +0 -237
- claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +0 -240
- claude_mpm/hooks/builtin/workflow_start_hook.py +0 -181
- claude_mpm/orchestration/__init__.py +0 -6
- claude_mpm/orchestration/archive/direct_orchestrator.py +0 -195
- claude_mpm/orchestration/archive/factory.py +0 -215
- claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +0 -188
- claude_mpm/orchestration/archive/hook_integration_example.py +0 -178
- claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +0 -826
- claude_mpm/orchestration/archive/orchestrator.py +0 -501
- claude_mpm/orchestration/archive/pexpect_orchestrator.py +0 -252
- claude_mpm/orchestration/archive/pty_orchestrator.py +0 -270
- claude_mpm/orchestration/archive/simple_orchestrator.py +0 -82
- claude_mpm/orchestration/archive/subprocess_orchestrator.py +0 -801
- claude_mpm/orchestration/archive/system_prompt_orchestrator.py +0 -278
- claude_mpm/orchestration/archive/wrapper_orchestrator.py +0 -187
- claude_mpm/schemas/workflow_validator.py +0 -411
- claude_mpm/services/parent_directory_manager/__init__.py +0 -577
- claude_mpm/services/parent_directory_manager/backup_manager.py +0 -258
- claude_mpm/services/parent_directory_manager/config_manager.py +0 -210
- claude_mpm/services/parent_directory_manager/deduplication_manager.py +0 -279
- claude_mpm/services/parent_directory_manager/framework_protector.py +0 -143
- claude_mpm/services/parent_directory_manager/operations.py +0 -186
- claude_mpm/services/parent_directory_manager/state_manager.py +0 -624
- claude_mpm/services/parent_directory_manager/template_deployer.py +0 -579
- claude_mpm/services/parent_directory_manager/validation_manager.py +0 -378
- claude_mpm/services/parent_directory_manager/version_control_helper.py +0 -339
- claude_mpm/services/parent_directory_manager/version_manager.py +0 -222
- claude_mpm/ui/__init__.py +0 -1
- claude_mpm/ui/rich_terminal_ui.py +0 -295
- claude_mpm/ui/terminal_ui.py +0 -328
- /claude_mpm/services/{agent_versioning.py → agents/deployment/agent_versioning.py} +0 -0
- /claude_mpm/services/{agent_capabilities_generator.py → agents/management/agent_capabilities_generator.py} +0 -0
- /claude_mpm/services/{agent_persistence_service.py → agents/memory/agent_persistence_service.py} +0 -0
- {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.dist-info}/WHEEL +0 -0
- {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.dist-info}/top_level.txt +0 -0
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Version Control Helper Module
|
|
4
|
-
=============================
|
|
5
|
-
|
|
6
|
-
This module provides version control utilities for the Parent Directory Manager.
|
|
7
|
-
It handles git operations and version control integration for the framework.
|
|
8
|
-
|
|
9
|
-
Key Features:
|
|
10
|
-
- Git repository detection
|
|
11
|
-
- Branch management helpers
|
|
12
|
-
- Commit status checking
|
|
13
|
-
- Version control integration utilities
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
import os
|
|
17
|
-
from pathlib import Path
|
|
18
|
-
from typing import Optional, List, Dict, Tuple, Any
|
|
19
|
-
import logging
|
|
20
|
-
from claude_pm.utils.subprocess_manager import SubprocessManager
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class VersionControlHelper:
|
|
24
|
-
"""
|
|
25
|
-
Helper class for version control operations.
|
|
26
|
-
|
|
27
|
-
This class provides utilities for interacting with git repositories
|
|
28
|
-
and managing version control operations within the framework.
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
def __init__(self, working_dir: Path, logger: Optional[logging.Logger] = None):
|
|
32
|
-
"""
|
|
33
|
-
Initialize the Version Control Helper.
|
|
34
|
-
|
|
35
|
-
Args:
|
|
36
|
-
working_dir: The working directory path
|
|
37
|
-
logger: Optional logger instance
|
|
38
|
-
"""
|
|
39
|
-
self.working_dir = Path(working_dir)
|
|
40
|
-
self.logger = logger or logging.getLogger(__name__)
|
|
41
|
-
|
|
42
|
-
# Initialize subprocess manager
|
|
43
|
-
self.subprocess_manager = SubprocessManager()
|
|
44
|
-
|
|
45
|
-
# Cache git repository detection
|
|
46
|
-
self._is_git_repo: Optional[bool] = None
|
|
47
|
-
self._git_root: Optional[Path] = None
|
|
48
|
-
|
|
49
|
-
def is_git_repository(self) -> bool:
|
|
50
|
-
"""
|
|
51
|
-
Check if the working directory is inside a git repository.
|
|
52
|
-
|
|
53
|
-
Returns:
|
|
54
|
-
True if inside a git repository, False otherwise
|
|
55
|
-
"""
|
|
56
|
-
if self._is_git_repo is not None:
|
|
57
|
-
return self._is_git_repo
|
|
58
|
-
|
|
59
|
-
try:
|
|
60
|
-
result = self.subprocess_manager.run(
|
|
61
|
-
["git", "rev-parse", "--git-dir"],
|
|
62
|
-
cwd=self.working_dir,
|
|
63
|
-
capture_output=True,
|
|
64
|
-
text=True
|
|
65
|
-
)
|
|
66
|
-
self._is_git_repo = result.success
|
|
67
|
-
|
|
68
|
-
if self._is_git_repo:
|
|
69
|
-
# Get the root of the git repository
|
|
70
|
-
root_result = self.subprocess_manager.run(
|
|
71
|
-
["git", "rev-parse", "--show-toplevel"],
|
|
72
|
-
cwd=self.working_dir,
|
|
73
|
-
capture_output=True,
|
|
74
|
-
text=True
|
|
75
|
-
)
|
|
76
|
-
if root_result.success:
|
|
77
|
-
self._git_root = Path(root_result.stdout.strip())
|
|
78
|
-
|
|
79
|
-
return self._is_git_repo
|
|
80
|
-
except (OSError, FileNotFoundError):
|
|
81
|
-
self._is_git_repo = False
|
|
82
|
-
return False
|
|
83
|
-
|
|
84
|
-
def get_git_root(self) -> Optional[Path]:
|
|
85
|
-
"""
|
|
86
|
-
Get the root directory of the git repository.
|
|
87
|
-
|
|
88
|
-
Returns:
|
|
89
|
-
Path to git root directory, or None if not in a git repo
|
|
90
|
-
"""
|
|
91
|
-
if self.is_git_repository():
|
|
92
|
-
return self._git_root
|
|
93
|
-
return None
|
|
94
|
-
|
|
95
|
-
def get_current_branch(self) -> Optional[str]:
|
|
96
|
-
"""
|
|
97
|
-
Get the current git branch name.
|
|
98
|
-
|
|
99
|
-
Returns:
|
|
100
|
-
Branch name or None if not in a git repository
|
|
101
|
-
"""
|
|
102
|
-
if not self.is_git_repository():
|
|
103
|
-
return None
|
|
104
|
-
|
|
105
|
-
try:
|
|
106
|
-
result = self.subprocess_manager.run(
|
|
107
|
-
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
108
|
-
cwd=self.working_dir,
|
|
109
|
-
capture_output=True,
|
|
110
|
-
text=True
|
|
111
|
-
)
|
|
112
|
-
if result.success:
|
|
113
|
-
return result.stdout.strip()
|
|
114
|
-
except Exception:
|
|
115
|
-
pass
|
|
116
|
-
|
|
117
|
-
return None
|
|
118
|
-
|
|
119
|
-
def has_uncommitted_changes(self) -> bool:
|
|
120
|
-
"""
|
|
121
|
-
Check if there are uncommitted changes in the repository.
|
|
122
|
-
|
|
123
|
-
Returns:
|
|
124
|
-
True if there are uncommitted changes, False otherwise
|
|
125
|
-
"""
|
|
126
|
-
if not self.is_git_repository():
|
|
127
|
-
return False
|
|
128
|
-
|
|
129
|
-
try:
|
|
130
|
-
result = self.subprocess_manager.run(
|
|
131
|
-
["git", "status", "--porcelain"],
|
|
132
|
-
cwd=self.working_dir,
|
|
133
|
-
capture_output=True,
|
|
134
|
-
text=True
|
|
135
|
-
)
|
|
136
|
-
if result.success:
|
|
137
|
-
return bool(result.stdout.strip())
|
|
138
|
-
except Exception:
|
|
139
|
-
pass
|
|
140
|
-
|
|
141
|
-
return False
|
|
142
|
-
|
|
143
|
-
def get_file_status(self, file_path: Path) -> Optional[str]:
|
|
144
|
-
"""
|
|
145
|
-
Get the git status of a specific file.
|
|
146
|
-
|
|
147
|
-
Args:
|
|
148
|
-
file_path: Path to the file to check
|
|
149
|
-
|
|
150
|
-
Returns:
|
|
151
|
-
Git status string or None if not tracked
|
|
152
|
-
"""
|
|
153
|
-
if not self.is_git_repository():
|
|
154
|
-
return None
|
|
155
|
-
|
|
156
|
-
try:
|
|
157
|
-
# Make path relative to git root for consistent results
|
|
158
|
-
git_root = self.get_git_root()
|
|
159
|
-
if git_root:
|
|
160
|
-
try:
|
|
161
|
-
relative_path = file_path.relative_to(git_root)
|
|
162
|
-
except ValueError:
|
|
163
|
-
# File is outside git repository
|
|
164
|
-
return None
|
|
165
|
-
else:
|
|
166
|
-
relative_path = file_path
|
|
167
|
-
|
|
168
|
-
result = self.subprocess_manager.run(
|
|
169
|
-
["git", "status", "--porcelain", str(relative_path)],
|
|
170
|
-
cwd=self.working_dir,
|
|
171
|
-
capture_output=True,
|
|
172
|
-
text=True
|
|
173
|
-
)
|
|
174
|
-
if result.success and result.stdout:
|
|
175
|
-
return result.stdout.strip()
|
|
176
|
-
except Exception:
|
|
177
|
-
pass
|
|
178
|
-
|
|
179
|
-
return None
|
|
180
|
-
|
|
181
|
-
def is_file_ignored(self, file_path: Path) -> bool:
|
|
182
|
-
"""
|
|
183
|
-
Check if a file is ignored by git.
|
|
184
|
-
|
|
185
|
-
Args:
|
|
186
|
-
file_path: Path to check
|
|
187
|
-
|
|
188
|
-
Returns:
|
|
189
|
-
True if file is ignored, False otherwise
|
|
190
|
-
"""
|
|
191
|
-
if not self.is_git_repository():
|
|
192
|
-
return False
|
|
193
|
-
|
|
194
|
-
try:
|
|
195
|
-
result = self.subprocess_manager.run(
|
|
196
|
-
["git", "check-ignore", str(file_path)],
|
|
197
|
-
cwd=self.working_dir,
|
|
198
|
-
capture_output=True
|
|
199
|
-
)
|
|
200
|
-
return result.success
|
|
201
|
-
except Exception:
|
|
202
|
-
return False
|
|
203
|
-
|
|
204
|
-
def add_to_gitignore(self, pattern: str) -> bool:
|
|
205
|
-
"""
|
|
206
|
-
Add a pattern to .gitignore file.
|
|
207
|
-
|
|
208
|
-
Args:
|
|
209
|
-
pattern: Pattern to add to gitignore
|
|
210
|
-
|
|
211
|
-
Returns:
|
|
212
|
-
True if successfully added, False otherwise
|
|
213
|
-
"""
|
|
214
|
-
if not self.is_git_repository():
|
|
215
|
-
self.logger.debug("Not in a git repository, skipping gitignore update")
|
|
216
|
-
return False
|
|
217
|
-
|
|
218
|
-
git_root = self.get_git_root()
|
|
219
|
-
if not git_root:
|
|
220
|
-
return False
|
|
221
|
-
|
|
222
|
-
gitignore_path = git_root / ".gitignore"
|
|
223
|
-
|
|
224
|
-
try:
|
|
225
|
-
# Read existing content
|
|
226
|
-
existing_patterns = set()
|
|
227
|
-
if gitignore_path.exists():
|
|
228
|
-
existing_patterns = set(gitignore_path.read_text().strip().split('\n'))
|
|
229
|
-
|
|
230
|
-
# Add pattern if not already present
|
|
231
|
-
if pattern not in existing_patterns:
|
|
232
|
-
with open(gitignore_path, 'a') as f:
|
|
233
|
-
if gitignore_path.exists() and gitignore_path.stat().st_size > 0:
|
|
234
|
-
f.write('\n')
|
|
235
|
-
f.write(f"{pattern}\n")
|
|
236
|
-
|
|
237
|
-
self.logger.info(f"Added '{pattern}' to .gitignore")
|
|
238
|
-
return True
|
|
239
|
-
else:
|
|
240
|
-
self.logger.debug(f"Pattern '{pattern}' already in .gitignore")
|
|
241
|
-
return True
|
|
242
|
-
|
|
243
|
-
except Exception as e:
|
|
244
|
-
self.logger.warning(f"Failed to update .gitignore: {e}")
|
|
245
|
-
return False
|
|
246
|
-
|
|
247
|
-
def get_last_commit_info(self) -> Optional[Dict[str, str]]:
|
|
248
|
-
"""
|
|
249
|
-
Get information about the last commit.
|
|
250
|
-
|
|
251
|
-
Returns:
|
|
252
|
-
Dictionary with commit info or None if not in a git repo
|
|
253
|
-
"""
|
|
254
|
-
if not self.is_git_repository():
|
|
255
|
-
return None
|
|
256
|
-
|
|
257
|
-
try:
|
|
258
|
-
# Get commit hash
|
|
259
|
-
hash_result = self.subprocess_manager.run(
|
|
260
|
-
["git", "rev-parse", "HEAD"],
|
|
261
|
-
cwd=self.working_dir,
|
|
262
|
-
capture_output=True,
|
|
263
|
-
text=True
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
# Get commit message
|
|
267
|
-
msg_result = self.subprocess_manager.run(
|
|
268
|
-
["git", "log", "-1", "--pretty=%B"],
|
|
269
|
-
cwd=self.working_dir,
|
|
270
|
-
capture_output=True,
|
|
271
|
-
text=True
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
# Get commit author
|
|
275
|
-
author_result = self.subprocess_manager.run(
|
|
276
|
-
["git", "log", "-1", "--pretty=%an <%ae>"],
|
|
277
|
-
cwd=self.working_dir,
|
|
278
|
-
capture_output=True,
|
|
279
|
-
text=True
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
# Get commit date
|
|
283
|
-
date_result = self.subprocess_manager.run(
|
|
284
|
-
["git", "log", "-1", "--pretty=%ai"],
|
|
285
|
-
cwd=self.working_dir,
|
|
286
|
-
capture_output=True,
|
|
287
|
-
text=True
|
|
288
|
-
)
|
|
289
|
-
|
|
290
|
-
if all(r.success for r in [hash_result, msg_result, author_result, date_result]):
|
|
291
|
-
return {
|
|
292
|
-
"hash": hash_result.stdout.strip(),
|
|
293
|
-
"message": msg_result.stdout.strip(),
|
|
294
|
-
"author": author_result.stdout.strip(),
|
|
295
|
-
"date": date_result.stdout.strip()
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
except Exception:
|
|
299
|
-
pass
|
|
300
|
-
|
|
301
|
-
return None
|
|
302
|
-
|
|
303
|
-
def create_version_tag(self, version: str, message: Optional[str] = None) -> bool:
|
|
304
|
-
"""
|
|
305
|
-
Create a git tag for a version.
|
|
306
|
-
|
|
307
|
-
Args:
|
|
308
|
-
version: Version string (e.g., "v1.0.0")
|
|
309
|
-
message: Optional tag message
|
|
310
|
-
|
|
311
|
-
Returns:
|
|
312
|
-
True if tag was created successfully
|
|
313
|
-
"""
|
|
314
|
-
if not self.is_git_repository():
|
|
315
|
-
return False
|
|
316
|
-
|
|
317
|
-
try:
|
|
318
|
-
if message:
|
|
319
|
-
cmd = ["git", "tag", "-a", version, "-m", message]
|
|
320
|
-
else:
|
|
321
|
-
cmd = ["git", "tag", version]
|
|
322
|
-
|
|
323
|
-
result = self.subprocess_manager.run(
|
|
324
|
-
cmd,
|
|
325
|
-
cwd=self.working_dir,
|
|
326
|
-
capture_output=True,
|
|
327
|
-
text=True
|
|
328
|
-
)
|
|
329
|
-
|
|
330
|
-
if result.success:
|
|
331
|
-
self.logger.info(f"Created git tag: {version}")
|
|
332
|
-
return True
|
|
333
|
-
else:
|
|
334
|
-
self.logger.warning(f"Failed to create git tag: {result.stderr}")
|
|
335
|
-
return False
|
|
336
|
-
|
|
337
|
-
except Exception as e:
|
|
338
|
-
self.logger.warning(f"Error creating git tag: {e}")
|
|
339
|
-
return False
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Version Manager - Handles subsystem version tracking and management
|
|
4
|
-
================================================================================
|
|
5
|
-
|
|
6
|
-
This module manages subsystem version tracking, loading, updating, and reporting
|
|
7
|
-
for the parent directory manager service.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
from typing import Dict, Any, Optional
|
|
12
|
-
from datetime import datetime
|
|
13
|
-
import logging
|
|
14
|
-
from ...utils.path_operations import path_ops
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class VersionManager:
|
|
18
|
-
"""Manages subsystem version tracking and operations."""
|
|
19
|
-
|
|
20
|
-
def __init__(
|
|
21
|
-
self,
|
|
22
|
-
framework_path: Path,
|
|
23
|
-
logger: Optional[logging.Logger] = None
|
|
24
|
-
):
|
|
25
|
-
"""
|
|
26
|
-
Initialize the Version Manager.
|
|
27
|
-
|
|
28
|
-
Args:
|
|
29
|
-
framework_path: Path to framework
|
|
30
|
-
logger: Logger instance to use
|
|
31
|
-
"""
|
|
32
|
-
self.framework_path = framework_path
|
|
33
|
-
self.logger = logger or logging.getLogger(__name__)
|
|
34
|
-
self.subsystem_versions: Dict[str, Dict[str, Any]] = {}
|
|
35
|
-
|
|
36
|
-
# Define subsystem version files
|
|
37
|
-
self.subsystem_files = {
|
|
38
|
-
"health": "HEALTH_VERSION"
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async def load_subsystem_versions(self) -> None:
|
|
42
|
-
"""Load subsystem versions from version files."""
|
|
43
|
-
try:
|
|
44
|
-
for subsystem, filename in self.subsystem_files.items():
|
|
45
|
-
version_file = self.framework_path / filename
|
|
46
|
-
if path_ops.validate_exists(version_file):
|
|
47
|
-
try:
|
|
48
|
-
version_content = path_ops.safe_read(version_file)
|
|
49
|
-
version = version_content.strip() if version_content else ""
|
|
50
|
-
self.subsystem_versions[subsystem] = {
|
|
51
|
-
"version": version,
|
|
52
|
-
"file_path": str(version_file),
|
|
53
|
-
"last_checked": datetime.now().isoformat()
|
|
54
|
-
}
|
|
55
|
-
self.logger.debug(f"Loaded {subsystem} version: {version}")
|
|
56
|
-
except Exception as e:
|
|
57
|
-
self.logger.error(f"Failed to read {subsystem} version from {version_file}: {e}")
|
|
58
|
-
self.subsystem_versions[subsystem] = {
|
|
59
|
-
"version": "unknown",
|
|
60
|
-
"file_path": str(version_file),
|
|
61
|
-
"error": str(e),
|
|
62
|
-
"last_checked": datetime.now().isoformat()
|
|
63
|
-
}
|
|
64
|
-
else:
|
|
65
|
-
self.logger.warning(f"Subsystem version file not found: {version_file}")
|
|
66
|
-
self.subsystem_versions[subsystem] = {
|
|
67
|
-
"version": "not_found",
|
|
68
|
-
"file_path": str(version_file),
|
|
69
|
-
"last_checked": datetime.now().isoformat()
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
self.logger.info(f"Loading subsystem versions...")
|
|
73
|
-
|
|
74
|
-
except Exception as e:
|
|
75
|
-
self.logger.error(f"Failed to load subsystem versions: {e}")
|
|
76
|
-
|
|
77
|
-
def get_subsystem_versions(self) -> Dict[str, Any]:
|
|
78
|
-
"""
|
|
79
|
-
Get all detected subsystem versions.
|
|
80
|
-
|
|
81
|
-
Returns:
|
|
82
|
-
Dictionary with subsystem version information
|
|
83
|
-
"""
|
|
84
|
-
return {
|
|
85
|
-
"framework_path": str(self.framework_path),
|
|
86
|
-
"subsystems": self.subsystem_versions.copy(),
|
|
87
|
-
"detection_timestamp": datetime.now().isoformat()
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
def get_subsystem_version(self, subsystem: str) -> Optional[str]:
|
|
91
|
-
"""
|
|
92
|
-
Get version for a specific subsystem.
|
|
93
|
-
|
|
94
|
-
Args:
|
|
95
|
-
subsystem: Name of the subsystem
|
|
96
|
-
|
|
97
|
-
Returns:
|
|
98
|
-
Version string or None if not found
|
|
99
|
-
"""
|
|
100
|
-
version_info = self.subsystem_versions.get(subsystem, {})
|
|
101
|
-
version = version_info.get("version")
|
|
102
|
-
|
|
103
|
-
# Return None for error states
|
|
104
|
-
if version in ["unknown", "not_found"]:
|
|
105
|
-
return None
|
|
106
|
-
|
|
107
|
-
return version
|
|
108
|
-
|
|
109
|
-
async def update_subsystem_version(
|
|
110
|
-
self,
|
|
111
|
-
subsystem: str,
|
|
112
|
-
new_version: str,
|
|
113
|
-
create_backup_func
|
|
114
|
-
) -> bool:
|
|
115
|
-
"""
|
|
116
|
-
Update a subsystem version file.
|
|
117
|
-
|
|
118
|
-
Args:
|
|
119
|
-
subsystem: Name of the subsystem
|
|
120
|
-
new_version: New version string
|
|
121
|
-
create_backup_func: Function to create backup of version file
|
|
122
|
-
|
|
123
|
-
Returns:
|
|
124
|
-
True if updated successfully, False otherwise
|
|
125
|
-
"""
|
|
126
|
-
try:
|
|
127
|
-
filename = self.subsystem_files.get(subsystem)
|
|
128
|
-
if not filename:
|
|
129
|
-
self.logger.error(f"Unknown subsystem: {subsystem}")
|
|
130
|
-
return False
|
|
131
|
-
|
|
132
|
-
version_file = self.framework_path / filename
|
|
133
|
-
|
|
134
|
-
# Backup existing version file if it exists
|
|
135
|
-
if version_file.exists():
|
|
136
|
-
backup_path = await create_backup_func(version_file)
|
|
137
|
-
if backup_path:
|
|
138
|
-
self.logger.info(f"Created backup of {filename}: {backup_path}")
|
|
139
|
-
|
|
140
|
-
# Write new version
|
|
141
|
-
version_file.write_text(new_version.strip())
|
|
142
|
-
|
|
143
|
-
# Update in-memory tracking
|
|
144
|
-
self.subsystem_versions[subsystem] = {
|
|
145
|
-
"version": new_version,
|
|
146
|
-
"file_path": str(version_file),
|
|
147
|
-
"last_updated": datetime.now().isoformat()
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
self.logger.info(f"Updated {subsystem} version to: {new_version}")
|
|
151
|
-
return True
|
|
152
|
-
|
|
153
|
-
except Exception as e:
|
|
154
|
-
self.logger.error(f"Failed to update {subsystem} version to {new_version}: {e}")
|
|
155
|
-
return False
|
|
156
|
-
|
|
157
|
-
def get_subsystem_version_report(self) -> Dict[str, Any]:
|
|
158
|
-
"""
|
|
159
|
-
Generate a comprehensive subsystem version report.
|
|
160
|
-
|
|
161
|
-
Returns:
|
|
162
|
-
Dictionary with detailed version information
|
|
163
|
-
"""
|
|
164
|
-
try:
|
|
165
|
-
report = {
|
|
166
|
-
"report_timestamp": datetime.now().isoformat(),
|
|
167
|
-
"framework_path": str(self.framework_path),
|
|
168
|
-
"subsystem_count": len(self.subsystem_versions),
|
|
169
|
-
"subsystems": {},
|
|
170
|
-
"summary": {
|
|
171
|
-
"total": 0,
|
|
172
|
-
"found": 0,
|
|
173
|
-
"missing": 0,
|
|
174
|
-
"errors": 0
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
for subsystem, info in self.subsystem_versions.items():
|
|
179
|
-
version = info.get("version", "unknown")
|
|
180
|
-
status = "found"
|
|
181
|
-
|
|
182
|
-
if version == "not_found":
|
|
183
|
-
status = "missing"
|
|
184
|
-
report["summary"]["missing"] += 1
|
|
185
|
-
elif version == "unknown" or "error" in info:
|
|
186
|
-
status = "error"
|
|
187
|
-
report["summary"]["errors"] += 1
|
|
188
|
-
else:
|
|
189
|
-
report["summary"]["found"] += 1
|
|
190
|
-
|
|
191
|
-
report["summary"]["total"] += 1
|
|
192
|
-
|
|
193
|
-
report["subsystems"][subsystem] = {
|
|
194
|
-
"version": version,
|
|
195
|
-
"status": status,
|
|
196
|
-
"file_path": info.get("file_path"),
|
|
197
|
-
"last_checked": info.get("last_checked"),
|
|
198
|
-
"error": info.get("error")
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return report
|
|
202
|
-
|
|
203
|
-
except Exception as e:
|
|
204
|
-
self.logger.error(f"Failed to generate subsystem version report: {e}")
|
|
205
|
-
return {
|
|
206
|
-
"error": str(e),
|
|
207
|
-
"report_timestamp": datetime.now().isoformat()
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
def get_version_report(self) -> Dict[str, Any]:
|
|
211
|
-
"""
|
|
212
|
-
Get a simple version report (for compatibility with VersionControlHelper).
|
|
213
|
-
|
|
214
|
-
Returns:
|
|
215
|
-
Dictionary with subsystem versions
|
|
216
|
-
"""
|
|
217
|
-
return {
|
|
218
|
-
"subsystems": {
|
|
219
|
-
subsystem: info.get("version", "unknown")
|
|
220
|
-
for subsystem, info in self.subsystem_versions.items()
|
|
221
|
-
}
|
|
222
|
-
}
|
claude_mpm/ui/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""Terminal UI components for claude-mpm."""
|