claude-mpm 3.4.27__py3-none-any.whl → 3.5.1__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 +5 -7
- 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.27.dist-info → claude_mpm-3.5.1.dist-info}/METADATA +28 -20
- {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.1.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.27.dist-info → claude_mpm-3.5.1.dist-info}/WHEEL +0 -0
- {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.1.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.1.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.1.dist-info}/top_level.txt +0 -0
|
@@ -1,579 +0,0 @@
|
|
|
1
|
-
"""Template deployment functionality for Claude PM Framework.
|
|
2
|
-
|
|
3
|
-
This module handles template deployment logic including version comparison,
|
|
4
|
-
template rendering, and deployment decision making.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import re
|
|
8
|
-
import platform
|
|
9
|
-
import hashlib
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
from typing import Dict, Any, Optional, Tuple
|
|
12
|
-
from datetime import datetime
|
|
13
|
-
from enum import Enum
|
|
14
|
-
|
|
15
|
-
# Import framework detection utilities
|
|
16
|
-
from ...utils.framework_detection import is_framework_source_directory
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class DeploymentContext(Enum):
|
|
20
|
-
"""Context for template deployment operations."""
|
|
21
|
-
FRAMEWORK = "framework"
|
|
22
|
-
PROJECT = "project"
|
|
23
|
-
USER = "user"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class TemplateDeployer:
|
|
27
|
-
"""Handles template deployment operations for Claude PM Framework."""
|
|
28
|
-
|
|
29
|
-
def __init__(self, framework_path: Path, logger):
|
|
30
|
-
"""Initialize template deployer.
|
|
31
|
-
|
|
32
|
-
Args:
|
|
33
|
-
framework_path: Path to framework directory
|
|
34
|
-
logger: Logger instance for output
|
|
35
|
-
"""
|
|
36
|
-
self.framework_path = framework_path
|
|
37
|
-
self.logger = logger
|
|
38
|
-
|
|
39
|
-
def should_skip_deployment(self, target_file: Path, template_content: str, force: bool) -> Tuple[bool, str]:
|
|
40
|
-
"""Determine if deployment should be skipped based on version comparison.
|
|
41
|
-
|
|
42
|
-
Args:
|
|
43
|
-
target_file: Target file path for deployment
|
|
44
|
-
template_content: Template content to deploy
|
|
45
|
-
force: Force deployment regardless of version
|
|
46
|
-
|
|
47
|
-
Returns:
|
|
48
|
-
Tuple of (should_skip, reason_message)
|
|
49
|
-
"""
|
|
50
|
-
if force:
|
|
51
|
-
return False, "Force flag set"
|
|
52
|
-
|
|
53
|
-
if not target_file.exists():
|
|
54
|
-
return False, "Target file does not exist"
|
|
55
|
-
|
|
56
|
-
# Check if this is a framework deployment template
|
|
57
|
-
if not self.is_framework_deployment_template(template_content):
|
|
58
|
-
return False, "Not a framework deployment template"
|
|
59
|
-
|
|
60
|
-
try:
|
|
61
|
-
# Read existing content
|
|
62
|
-
existing_content = target_file.read_text(encoding='utf-8')
|
|
63
|
-
|
|
64
|
-
# Extract versions
|
|
65
|
-
template_version = self.extract_claude_md_version(template_content)
|
|
66
|
-
existing_version = self.extract_claude_md_version(existing_content)
|
|
67
|
-
|
|
68
|
-
if not template_version or not existing_version:
|
|
69
|
-
return False, "Could not extract versions for comparison"
|
|
70
|
-
|
|
71
|
-
# Compare versions
|
|
72
|
-
comparison = self.compare_versions(template_version, existing_version)
|
|
73
|
-
|
|
74
|
-
if comparison <= 0:
|
|
75
|
-
return True, f"Template version {template_version} is not newer than existing {existing_version}"
|
|
76
|
-
|
|
77
|
-
return False, f"Template version {template_version} is newer than existing {existing_version}"
|
|
78
|
-
|
|
79
|
-
except Exception as e:
|
|
80
|
-
self.logger.debug(f"Error during version comparison: {e}")
|
|
81
|
-
return False, f"Error during version comparison: {e}"
|
|
82
|
-
|
|
83
|
-
def is_framework_deployment_template(self, content: str) -> bool:
|
|
84
|
-
"""Check if content is a framework deployment template.
|
|
85
|
-
|
|
86
|
-
Args:
|
|
87
|
-
content: Content to check
|
|
88
|
-
|
|
89
|
-
Returns:
|
|
90
|
-
True if content is a framework deployment template
|
|
91
|
-
"""
|
|
92
|
-
# Check for the specific deployment header
|
|
93
|
-
return "# Claude PM Framework Configuration - Deployment" in content
|
|
94
|
-
|
|
95
|
-
def extract_claude_md_version(self, content: str) -> Optional[str]:
|
|
96
|
-
"""Extract CLAUDE_MD_VERSION from content.
|
|
97
|
-
|
|
98
|
-
Args:
|
|
99
|
-
content: Content to extract version from
|
|
100
|
-
|
|
101
|
-
Returns:
|
|
102
|
-
Version string if found, None otherwise
|
|
103
|
-
"""
|
|
104
|
-
# Look for CLAUDE_MD_VERSION in metadata comment
|
|
105
|
-
version_pattern = r'CLAUDE_MD_VERSION:\s*([^\s\n]+)'
|
|
106
|
-
match = re.search(version_pattern, content)
|
|
107
|
-
|
|
108
|
-
if match:
|
|
109
|
-
return match.group(1).strip()
|
|
110
|
-
|
|
111
|
-
return None
|
|
112
|
-
|
|
113
|
-
def compare_versions(self, version1: str, version2: str) -> int:
|
|
114
|
-
"""Compare two version strings.
|
|
115
|
-
|
|
116
|
-
Handles both semantic versions (e.g., "1.2.3") and
|
|
117
|
-
framework versions with serial numbers (e.g., "014-004").
|
|
118
|
-
|
|
119
|
-
Args:
|
|
120
|
-
version1: First version to compare
|
|
121
|
-
version2: Second version to compare
|
|
122
|
-
|
|
123
|
-
Returns:
|
|
124
|
-
-1 if version1 < version2
|
|
125
|
-
0 if version1 == version2
|
|
126
|
-
1 if version1 > version2
|
|
127
|
-
"""
|
|
128
|
-
# First try framework version format (e.g., "014-004")
|
|
129
|
-
framework_pattern = r'^(\d+)-(\d+)$'
|
|
130
|
-
|
|
131
|
-
match1 = re.match(framework_pattern, version1)
|
|
132
|
-
match2 = re.match(framework_pattern, version2)
|
|
133
|
-
|
|
134
|
-
if match1 and match2:
|
|
135
|
-
# Compare framework versions
|
|
136
|
-
major1, minor1 = int(match1.group(1)), int(match1.group(2))
|
|
137
|
-
major2, minor2 = int(match2.group(1)), int(match2.group(2))
|
|
138
|
-
|
|
139
|
-
if major1 != major2:
|
|
140
|
-
return 1 if major1 > major2 else -1
|
|
141
|
-
if minor1 != minor2:
|
|
142
|
-
return 1 if minor1 > minor2 else -1
|
|
143
|
-
return 0
|
|
144
|
-
|
|
145
|
-
# Try semantic version format (e.g., "1.2.3")
|
|
146
|
-
semantic_pattern = r'^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$'
|
|
147
|
-
|
|
148
|
-
match1 = re.match(semantic_pattern, version1)
|
|
149
|
-
match2 = re.match(semantic_pattern, version2)
|
|
150
|
-
|
|
151
|
-
if match1 and match2:
|
|
152
|
-
# Compare semantic versions
|
|
153
|
-
for i in range(1, 4):
|
|
154
|
-
v1 = int(match1.group(i))
|
|
155
|
-
v2 = int(match2.group(i))
|
|
156
|
-
if v1 != v2:
|
|
157
|
-
return 1 if v1 > v2 else -1
|
|
158
|
-
|
|
159
|
-
# Compare pre-release versions if present
|
|
160
|
-
pre1 = match1.group(4)
|
|
161
|
-
pre2 = match2.group(4)
|
|
162
|
-
|
|
163
|
-
if pre1 and not pre2:
|
|
164
|
-
return -1 # pre-release is less than release
|
|
165
|
-
elif not pre1 and pre2:
|
|
166
|
-
return 1 # release is greater than pre-release
|
|
167
|
-
elif pre1 and pre2:
|
|
168
|
-
# Simple string comparison for pre-release
|
|
169
|
-
if pre1 < pre2:
|
|
170
|
-
return -1
|
|
171
|
-
elif pre1 > pre2:
|
|
172
|
-
return 1
|
|
173
|
-
|
|
174
|
-
return 0
|
|
175
|
-
|
|
176
|
-
# Fallback to string comparison
|
|
177
|
-
if version1 < version2:
|
|
178
|
-
return -1
|
|
179
|
-
elif version1 > version2:
|
|
180
|
-
return 1
|
|
181
|
-
return 0
|
|
182
|
-
|
|
183
|
-
def get_platform_notes(self) -> str:
|
|
184
|
-
"""Get platform-specific notes for the current system.
|
|
185
|
-
|
|
186
|
-
Returns:
|
|
187
|
-
Platform-specific notes string
|
|
188
|
-
"""
|
|
189
|
-
system = platform.system().lower()
|
|
190
|
-
|
|
191
|
-
if system == 'darwin':
|
|
192
|
-
return """
|
|
193
|
-
- **macOS Users**: Ensure Python 3.8+ is installed via Homebrew or official installer
|
|
194
|
-
- **Command Shortcuts**: Add aliases to ~/.zshrc or ~/.bash_profile
|
|
195
|
-
- **Permissions**: May need to grant Terminal permissions for file access
|
|
196
|
-
"""
|
|
197
|
-
elif system == 'linux':
|
|
198
|
-
return """
|
|
199
|
-
- **Linux Users**: Ensure Python 3.8+ is installed via package manager
|
|
200
|
-
- **Command Shortcuts**: Add aliases to ~/.bashrc or ~/.zshrc
|
|
201
|
-
- **Permissions**: Use appropriate user permissions for .claude-pm directories
|
|
202
|
-
"""
|
|
203
|
-
elif system == 'windows':
|
|
204
|
-
return """
|
|
205
|
-
- **Windows Users**: Ensure Python 3.8+ is installed and added to PATH
|
|
206
|
-
- **Command Shortcuts**: Use PowerShell aliases or batch files
|
|
207
|
-
- **Permissions**: Run as Administrator if encountering permission issues
|
|
208
|
-
"""
|
|
209
|
-
else:
|
|
210
|
-
return """
|
|
211
|
-
- **Platform**: Ensure Python 3.8+ is installed and accessible
|
|
212
|
-
- **Permissions**: Ensure appropriate file system permissions
|
|
213
|
-
"""
|
|
214
|
-
|
|
215
|
-
def get_deployment_variables(self, deployment_id: str) -> Dict[str, Any]:
|
|
216
|
-
"""Get variables for template deployment.
|
|
217
|
-
|
|
218
|
-
Args:
|
|
219
|
-
deployment_id: Unique deployment identifier
|
|
220
|
-
|
|
221
|
-
Returns:
|
|
222
|
-
Dictionary of deployment variables
|
|
223
|
-
"""
|
|
224
|
-
now = datetime.now()
|
|
225
|
-
|
|
226
|
-
return {
|
|
227
|
-
'DEPLOYMENT_ID': deployment_id,
|
|
228
|
-
'DEPLOYMENT_DATE': now.isoformat(),
|
|
229
|
-
'PLATFORM_NOTES': self.get_platform_notes(),
|
|
230
|
-
'FRAMEWORK_VERSION': self._get_framework_version(),
|
|
231
|
-
'PYTHON_VERSION': platform.python_version(),
|
|
232
|
-
'SYSTEM': platform.system(),
|
|
233
|
-
'HOSTNAME': platform.node(),
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
def render_template_content(self, content: str, variables: Dict[str, Any]) -> str:
|
|
237
|
-
"""Render template content with handlebars-style variable substitution.
|
|
238
|
-
|
|
239
|
-
Args:
|
|
240
|
-
content: Template content with {{VARIABLE}} placeholders
|
|
241
|
-
variables: Dictionary of variable replacements
|
|
242
|
-
|
|
243
|
-
Returns:
|
|
244
|
-
Rendered content with variables substituted
|
|
245
|
-
"""
|
|
246
|
-
rendered = content
|
|
247
|
-
|
|
248
|
-
# Replace handlebars variables
|
|
249
|
-
for key, value in variables.items():
|
|
250
|
-
placeholder = f"{{{{{key}}}}}"
|
|
251
|
-
rendered = rendered.replace(placeholder, str(value))
|
|
252
|
-
|
|
253
|
-
return rendered
|
|
254
|
-
|
|
255
|
-
def _get_framework_version(self) -> str:
|
|
256
|
-
"""Get framework version from VERSION file.
|
|
257
|
-
|
|
258
|
-
Returns:
|
|
259
|
-
Framework version string
|
|
260
|
-
"""
|
|
261
|
-
version_file = self.framework_path / "VERSION"
|
|
262
|
-
|
|
263
|
-
if version_file.exists():
|
|
264
|
-
try:
|
|
265
|
-
return version_file.read_text().strip()
|
|
266
|
-
except Exception:
|
|
267
|
-
pass
|
|
268
|
-
|
|
269
|
-
# Fallback version
|
|
270
|
-
return "0.7.0"
|
|
271
|
-
|
|
272
|
-
async def deploy_framework_template(
|
|
273
|
-
self,
|
|
274
|
-
target_directory: Path,
|
|
275
|
-
force: bool = False,
|
|
276
|
-
deduplication_handler=None,
|
|
277
|
-
backup_manager=None,
|
|
278
|
-
state_manager=None,
|
|
279
|
-
quiet: bool = False
|
|
280
|
-
) -> Tuple[bool, Optional[Path], Optional[str], list]:
|
|
281
|
-
"""Deploy framework template with integrated deployment.
|
|
282
|
-
|
|
283
|
-
Args:
|
|
284
|
-
target_directory: Directory to deploy template to
|
|
285
|
-
force: Force deployment even if version is current
|
|
286
|
-
deduplication_handler: Handler for deduplication operations
|
|
287
|
-
backup_manager: Manager for backup operations
|
|
288
|
-
state_manager: Manager for state operations
|
|
289
|
-
quiet: Whether to suppress info messages
|
|
290
|
-
|
|
291
|
-
Returns:
|
|
292
|
-
Tuple of (success, target_path, error_message, changes_made)
|
|
293
|
-
"""
|
|
294
|
-
from ..framework_claude_md_generator import FrameworkClaudeMdGenerator
|
|
295
|
-
import hashlib
|
|
296
|
-
|
|
297
|
-
changes_made = []
|
|
298
|
-
# Check for INSTRUCTIONS.md first, fallback to CLAUDE.md for backward compatibility
|
|
299
|
-
target_path = target_directory / "INSTRUCTIONS.md"
|
|
300
|
-
legacy_path = target_directory / "CLAUDE.md"
|
|
301
|
-
|
|
302
|
-
# If CLAUDE.md exists but not INSTRUCTIONS.md, use CLAUDE.md path for compatibility
|
|
303
|
-
if not target_path.exists() and legacy_path.exists():
|
|
304
|
-
target_path = legacy_path
|
|
305
|
-
backup_path = None
|
|
306
|
-
|
|
307
|
-
try:
|
|
308
|
-
# Check if we're in the framework source directory
|
|
309
|
-
is_framework, markers = is_framework_source_directory(target_directory)
|
|
310
|
-
if is_framework:
|
|
311
|
-
self.logger.warning(f"🚫 Skipping INSTRUCTIONS.md/CLAUDE.md deployment - detected framework source directory")
|
|
312
|
-
self.logger.debug(f"Framework markers found: {', '.join(markers)}")
|
|
313
|
-
return True, target_path, None, [] # Return success but with no changes
|
|
314
|
-
|
|
315
|
-
# Run deduplication if handler provided
|
|
316
|
-
if deduplication_handler:
|
|
317
|
-
self.logger.info("🔍 Running INSTRUCTIONS.md/CLAUDE.md deduplication before deployment...")
|
|
318
|
-
dedup_actions = await deduplication_handler()
|
|
319
|
-
if dedup_actions:
|
|
320
|
-
self.logger.info(f"📋 Deduplication processed {len(dedup_actions)} files")
|
|
321
|
-
|
|
322
|
-
# Use the generator's deploy_to_parent method
|
|
323
|
-
generator = FrameworkClaudeMdGenerator()
|
|
324
|
-
|
|
325
|
-
# Set template variables for deployment
|
|
326
|
-
generator.template_variables = self.get_deployment_variables("{{DEPLOYMENT_ID}}")
|
|
327
|
-
|
|
328
|
-
# Create backup if file exists
|
|
329
|
-
if target_path.exists() and backup_manager:
|
|
330
|
-
existing_content = target_path.read_text()
|
|
331
|
-
|
|
332
|
-
# Check if existing file is protected
|
|
333
|
-
is_framework_template = self.is_framework_deployment_template(existing_content)
|
|
334
|
-
|
|
335
|
-
if not is_framework_template:
|
|
336
|
-
# This is a project development file - PERMANENT PROTECTION
|
|
337
|
-
error_msg = "Permanent protection active: Existing file is not a framework deployment template - protecting project development file"
|
|
338
|
-
self.logger.error(f"🚫 PERMANENT PROTECTION: {error_msg}")
|
|
339
|
-
return False, target_path, error_msg, []
|
|
340
|
-
|
|
341
|
-
# Create backup since it's a framework template
|
|
342
|
-
# Use parent_directory_manager backup directory
|
|
343
|
-
backups_dir = backup_manager.base_dir / ".claude-pm" / "backups" / "parent_directory_manager"
|
|
344
|
-
backup_path = backup_manager.create_backup(target_path, backups_dir)
|
|
345
|
-
if backup_path and not quiet:
|
|
346
|
-
self.logger.info(f"📁 Backup created: {backup_path}")
|
|
347
|
-
|
|
348
|
-
# Deploy using generator
|
|
349
|
-
success, message = generator.deploy_to_parent(target_directory, force=force)
|
|
350
|
-
|
|
351
|
-
if success:
|
|
352
|
-
changes_made.append(f"Deployed framework template to {target_path}")
|
|
353
|
-
return True, target_path, None, changes_made
|
|
354
|
-
else:
|
|
355
|
-
return False, target_path, f"Deployment failed: {message}", []
|
|
356
|
-
|
|
357
|
-
except Exception as e:
|
|
358
|
-
self.logger.error(f"Failed to deploy framework template to {target_directory}: {e}")
|
|
359
|
-
return False, target_path, str(e), []
|
|
360
|
-
|
|
361
|
-
async def install_template(
|
|
362
|
-
self,
|
|
363
|
-
target_directory: Path,
|
|
364
|
-
template_id: str,
|
|
365
|
-
template_variables: Dict[str, Any] = None,
|
|
366
|
-
force: bool = False,
|
|
367
|
-
deduplication_handler=None,
|
|
368
|
-
backup_manager=None,
|
|
369
|
-
state_manager=None,
|
|
370
|
-
quiet: bool = False,
|
|
371
|
-
current_target_file: Optional[Path] = None
|
|
372
|
-
) -> Tuple[bool, Optional[Path], Optional[str], Optional[str], list]:
|
|
373
|
-
"""Install a template to a directory with version checking.
|
|
374
|
-
|
|
375
|
-
Args:
|
|
376
|
-
target_directory: Directory to install template to
|
|
377
|
-
template_id: Template to install
|
|
378
|
-
template_variables: Variables for template rendering
|
|
379
|
-
force: Force installation even if version is current
|
|
380
|
-
deduplication_handler: Handler for deduplication operations
|
|
381
|
-
backup_manager: Manager for backup operations
|
|
382
|
-
state_manager: Manager for state operations
|
|
383
|
-
quiet: Whether to suppress info messages
|
|
384
|
-
current_target_file: Current target file for version auto-increment
|
|
385
|
-
|
|
386
|
-
Returns:
|
|
387
|
-
Tuple of (success, target_path, version, error_message, changes_made)
|
|
388
|
-
"""
|
|
389
|
-
from ..framework_claude_md_generator import FrameworkClaudeMdGenerator
|
|
390
|
-
import hashlib
|
|
391
|
-
|
|
392
|
-
changes_made = []
|
|
393
|
-
# Check for INSTRUCTIONS.md first, fallback to CLAUDE.md for backward compatibility
|
|
394
|
-
target_file = target_directory / "INSTRUCTIONS.md"
|
|
395
|
-
legacy_file = target_directory / "CLAUDE.md"
|
|
396
|
-
|
|
397
|
-
# If CLAUDE.md exists but not INSTRUCTIONS.md, use CLAUDE.md path for compatibility
|
|
398
|
-
if not target_file.exists() and legacy_file.exists():
|
|
399
|
-
target_file = legacy_file
|
|
400
|
-
|
|
401
|
-
# Check if we're in the framework source directory
|
|
402
|
-
is_framework, markers = is_framework_source_directory(target_directory)
|
|
403
|
-
if is_framework:
|
|
404
|
-
self.logger.warning(f"🚫 Skipping INSTRUCTIONS.md/CLAUDE.md installation - detected framework source directory")
|
|
405
|
-
self.logger.debug(f"Framework markers found: {', '.join(markers)}")
|
|
406
|
-
return True, target_file, None, None, [f"Installation skipped: framework source directory detected"]
|
|
407
|
-
backup_path = None
|
|
408
|
-
|
|
409
|
-
try:
|
|
410
|
-
# Run deduplication if handler provided
|
|
411
|
-
if deduplication_handler:
|
|
412
|
-
self.logger.info("🔍 Running INSTRUCTIONS.md/CLAUDE.md deduplication before installation...")
|
|
413
|
-
dedup_actions = await deduplication_handler()
|
|
414
|
-
if dedup_actions:
|
|
415
|
-
self.logger.info(f"📋 Deduplication processed {len(dedup_actions)} files")
|
|
416
|
-
|
|
417
|
-
# Get template content using generator
|
|
418
|
-
content, template_version = await self._generate_framework_template(
|
|
419
|
-
template_id, current_target_file
|
|
420
|
-
)
|
|
421
|
-
|
|
422
|
-
if not content:
|
|
423
|
-
raise RuntimeError(
|
|
424
|
-
"Template manager removed - use Claude Code Task Tool for template management"
|
|
425
|
-
)
|
|
426
|
-
|
|
427
|
-
# Create backup if file exists
|
|
428
|
-
if target_file.exists() and backup_manager:
|
|
429
|
-
existing_content = target_file.read_text()
|
|
430
|
-
# Use parent_directory_manager backup directory
|
|
431
|
-
backups_dir = backup_manager.base_dir / ".claude-pm" / "backups" / "parent_directory_manager"
|
|
432
|
-
backup_path = backup_manager.create_backup(target_file, backups_dir)
|
|
433
|
-
if backup_path and not quiet:
|
|
434
|
-
self.logger.info(f"📁 Backup created: {backup_path}")
|
|
435
|
-
|
|
436
|
-
# Check if deployment should be skipped
|
|
437
|
-
should_skip, skip_reason = self.should_skip_deployment(target_file, content, force)
|
|
438
|
-
is_permanent_protection = False
|
|
439
|
-
|
|
440
|
-
if target_file.exists():
|
|
441
|
-
existing_content = target_file.read_text()
|
|
442
|
-
if not self.is_framework_deployment_template(existing_content):
|
|
443
|
-
is_permanent_protection = True
|
|
444
|
-
skip_reason = "Existing file is not a framework deployment template"
|
|
445
|
-
should_skip = True
|
|
446
|
-
|
|
447
|
-
if should_skip:
|
|
448
|
-
if is_permanent_protection:
|
|
449
|
-
# PERMANENT PROTECTION
|
|
450
|
-
self.logger.error(f"🚫 PERMANENT PROTECTION: {skip_reason}")
|
|
451
|
-
return False, target_file, None, f"Permanent protection active: {skip_reason}", []
|
|
452
|
-
elif not force:
|
|
453
|
-
# OVERRIDABLE PROTECTION
|
|
454
|
-
self.logger.info(f"Skipped template installation: {skip_reason}")
|
|
455
|
-
return True, target_file, template_version, None, [f"Deployment skipped: {skip_reason}"]
|
|
456
|
-
else:
|
|
457
|
-
# FORCE OVERRIDE
|
|
458
|
-
self.logger.warning(f"⚡ FORCE FLAG ACTIVE: Overriding version protection - {skip_reason}")
|
|
459
|
-
|
|
460
|
-
# Write the content
|
|
461
|
-
target_file.write_text(content)
|
|
462
|
-
changes_made.append(f"Installed template {template_id} to {target_file}")
|
|
463
|
-
|
|
464
|
-
if not quiet:
|
|
465
|
-
self.logger.info(f"Successfully installed template {template_id} to {target_file}")
|
|
466
|
-
|
|
467
|
-
return True, target_file, template_version, None, changes_made
|
|
468
|
-
|
|
469
|
-
except Exception as e:
|
|
470
|
-
self.logger.error(f"Failed to install template {template_id} to {target_directory}: {e}")
|
|
471
|
-
# Return the appropriate target path
|
|
472
|
-
target_file = target_directory / "INSTRUCTIONS.md"
|
|
473
|
-
if not target_file.exists() and (target_directory / "CLAUDE.md").exists():
|
|
474
|
-
target_file = target_directory / "CLAUDE.md"
|
|
475
|
-
return False, target_file, None, str(e), []
|
|
476
|
-
|
|
477
|
-
async def _generate_framework_template(
|
|
478
|
-
self, template_id: str, current_target_file: Optional[Path] = None
|
|
479
|
-
) -> Tuple[Optional[str], Optional[str]]:
|
|
480
|
-
"""Generate framework template content.
|
|
481
|
-
|
|
482
|
-
Args:
|
|
483
|
-
template_id: Template identifier
|
|
484
|
-
current_target_file: Current target file for version auto-increment
|
|
485
|
-
|
|
486
|
-
Returns:
|
|
487
|
-
Tuple of (content, version)
|
|
488
|
-
"""
|
|
489
|
-
from ..framework_claude_md_generator import FrameworkClaudeMdGenerator
|
|
490
|
-
|
|
491
|
-
# Check for framework INSTRUCTIONS.md/CLAUDE.md template
|
|
492
|
-
if template_id in ["parent_directory_claude_md", "claude_md", "deployment_claude"]:
|
|
493
|
-
# Use the generator to create the template
|
|
494
|
-
generator = FrameworkClaudeMdGenerator()
|
|
495
|
-
|
|
496
|
-
# Set template variables before generation
|
|
497
|
-
generator.template_variables = self.get_deployment_variables("{{DEPLOYMENT_ID}}")
|
|
498
|
-
|
|
499
|
-
# Check if we have an existing file for version auto-increment
|
|
500
|
-
current_content = None
|
|
501
|
-
if current_target_file and current_target_file.exists():
|
|
502
|
-
current_content = current_target_file.read_text()
|
|
503
|
-
|
|
504
|
-
# Generate the template content
|
|
505
|
-
content = generator.generate(current_content=current_content)
|
|
506
|
-
|
|
507
|
-
# Extract the version that was generated
|
|
508
|
-
generated_version = self.extract_claude_md_version(content)
|
|
509
|
-
|
|
510
|
-
return content, generated_version
|
|
511
|
-
|
|
512
|
-
return None, None
|
|
513
|
-
|
|
514
|
-
async def get_framework_template(
|
|
515
|
-
self,
|
|
516
|
-
template_id: str,
|
|
517
|
-
current_target_file: Optional[Path],
|
|
518
|
-
backup_manager,
|
|
519
|
-
log_info_func
|
|
520
|
-
) -> Tuple[Optional[str], Optional[Any]]:
|
|
521
|
-
"""Get template from deployment framework path using the new generator.
|
|
522
|
-
|
|
523
|
-
Args:
|
|
524
|
-
template_id: Template identifier
|
|
525
|
-
current_target_file: Current target file for version auto-increment
|
|
526
|
-
backup_manager: Backup manager instance
|
|
527
|
-
log_info_func: Function to log info messages
|
|
528
|
-
|
|
529
|
-
Returns:
|
|
530
|
-
Tuple of (content, template_version)
|
|
531
|
-
"""
|
|
532
|
-
content, version = await self._generate_framework_template(
|
|
533
|
-
template_id, current_target_file
|
|
534
|
-
)
|
|
535
|
-
|
|
536
|
-
if content:
|
|
537
|
-
# Maintain backup functionality with the generated content
|
|
538
|
-
# Try INSTRUCTIONS.md first, then fall back to CLAUDE.md
|
|
539
|
-
framework_template_path = self.framework_path / "agents" / "INSTRUCTIONS.md"
|
|
540
|
-
if not framework_template_path.exists():
|
|
541
|
-
framework_template_path = self.framework_path / "agents" / "CLAUDE.md"
|
|
542
|
-
# For wheel installations, check data directory
|
|
543
|
-
if not framework_template_path.exists():
|
|
544
|
-
# Check data directory for both INSTRUCTIONS.md and CLAUDE.md
|
|
545
|
-
data_template_path = self.framework_path / "data" / "agents" / "INSTRUCTIONS.md"
|
|
546
|
-
if not data_template_path.exists():
|
|
547
|
-
data_template_path = self.framework_path / "data" / "agents" / "CLAUDE.md"
|
|
548
|
-
if data_template_path.exists():
|
|
549
|
-
framework_template_path = data_template_path
|
|
550
|
-
|
|
551
|
-
if framework_template_path.exists():
|
|
552
|
-
# BACKUP: Create backup before any operations
|
|
553
|
-
backup_manager.backup_framework_template(framework_template_path)
|
|
554
|
-
|
|
555
|
-
# Create a simple template version object for compatibility
|
|
556
|
-
class SimpleTemplateVersion:
|
|
557
|
-
def __init__(self, template_id, version, source, created_at, checksum, variables, metadata):
|
|
558
|
-
self.template_id = template_id
|
|
559
|
-
self.version = version
|
|
560
|
-
self.source = source
|
|
561
|
-
self.created_at = created_at
|
|
562
|
-
self.checksum = checksum
|
|
563
|
-
self.variables = variables
|
|
564
|
-
self.metadata = metadata
|
|
565
|
-
|
|
566
|
-
template_version = SimpleTemplateVersion(
|
|
567
|
-
template_id=template_id,
|
|
568
|
-
version=version or "deployment-current",
|
|
569
|
-
source="framework-generator",
|
|
570
|
-
created_at=datetime.now(),
|
|
571
|
-
checksum=hashlib.sha256(content.encode()).hexdigest(),
|
|
572
|
-
variables=self.get_deployment_variables("{{DEPLOYMENT_ID}}"),
|
|
573
|
-
metadata={"source": "framework-generator"}
|
|
574
|
-
)
|
|
575
|
-
|
|
576
|
-
log_info_func(f"Using framework template from generator (version: {version})")
|
|
577
|
-
return content, template_version
|
|
578
|
-
|
|
579
|
-
return None, None
|