claude-mpm 3.4.27__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.
Files changed (123) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +182 -299
  3. claude_mpm/agents/agent_loader.py +283 -57
  4. claude_mpm/agents/agent_loader_integration.py +6 -9
  5. claude_mpm/agents/base_agent.json +2 -1
  6. claude_mpm/agents/base_agent_loader.py +1 -1
  7. claude_mpm/cli/__init__.py +5 -7
  8. claude_mpm/cli/commands/__init__.py +0 -2
  9. claude_mpm/cli/commands/agents.py +1 -1
  10. claude_mpm/cli/commands/memory.py +1 -1
  11. claude_mpm/cli/commands/run.py +12 -0
  12. claude_mpm/cli/parser.py +0 -13
  13. claude_mpm/cli/utils.py +1 -1
  14. claude_mpm/config/__init__.py +44 -2
  15. claude_mpm/config/agent_config.py +348 -0
  16. claude_mpm/config/paths.py +322 -0
  17. claude_mpm/constants.py +0 -1
  18. claude_mpm/core/__init__.py +2 -5
  19. claude_mpm/core/agent_registry.py +63 -17
  20. claude_mpm/core/claude_runner.py +354 -43
  21. claude_mpm/core/config.py +7 -1
  22. claude_mpm/core/config_aliases.py +4 -3
  23. claude_mpm/core/config_paths.py +151 -0
  24. claude_mpm/core/factories.py +4 -50
  25. claude_mpm/core/logger.py +11 -13
  26. claude_mpm/core/service_registry.py +2 -2
  27. claude_mpm/dashboard/static/js/components/agent-inference.js +101 -25
  28. claude_mpm/dashboard/static/js/components/event-processor.js +3 -2
  29. claude_mpm/hooks/claude_hooks/hook_handler.py +343 -83
  30. claude_mpm/hooks/memory_integration_hook.py +1 -1
  31. claude_mpm/init.py +37 -6
  32. claude_mpm/scripts/socketio_daemon.py +6 -2
  33. claude_mpm/services/__init__.py +71 -3
  34. claude_mpm/services/agents/__init__.py +85 -0
  35. claude_mpm/services/agents/deployment/__init__.py +21 -0
  36. claude_mpm/services/{agent_deployment.py → agents/deployment/agent_deployment.py} +192 -41
  37. claude_mpm/services/{agent_lifecycle_manager.py → agents/deployment/agent_lifecycle_manager.py} +11 -10
  38. claude_mpm/services/agents/loading/__init__.py +11 -0
  39. claude_mpm/services/{agent_profile_loader.py → agents/loading/agent_profile_loader.py} +9 -8
  40. claude_mpm/services/{base_agent_manager.py → agents/loading/base_agent_manager.py} +2 -2
  41. claude_mpm/services/{framework_agent_loader.py → agents/loading/framework_agent_loader.py} +116 -40
  42. claude_mpm/services/agents/management/__init__.py +9 -0
  43. claude_mpm/services/{agent_management_service.py → agents/management/agent_management_service.py} +6 -5
  44. claude_mpm/services/agents/memory/__init__.py +21 -0
  45. claude_mpm/services/{agent_memory_manager.py → agents/memory/agent_memory_manager.py} +3 -3
  46. claude_mpm/services/agents/registry/__init__.py +29 -0
  47. claude_mpm/services/{agent_registry.py → agents/registry/agent_registry.py} +101 -16
  48. claude_mpm/services/{deployed_agent_discovery.py → agents/registry/deployed_agent_discovery.py} +12 -2
  49. claude_mpm/services/{agent_modification_tracker.py → agents/registry/modification_tracker.py} +6 -5
  50. claude_mpm/services/async_session_logger.py +584 -0
  51. claude_mpm/services/claude_session_logger.py +299 -0
  52. claude_mpm/services/framework_claude_md_generator/content_assembler.py +2 -2
  53. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +17 -17
  54. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +3 -3
  55. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +1 -1
  56. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +1 -1
  57. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +19 -24
  58. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +1 -1
  59. claude_mpm/services/framework_claude_md_generator.py +4 -2
  60. claude_mpm/services/memory/__init__.py +17 -0
  61. claude_mpm/services/{memory_builder.py → memory/builder.py} +3 -3
  62. claude_mpm/services/memory/cache/__init__.py +14 -0
  63. claude_mpm/services/{shared_prompt_cache.py → memory/cache/shared_prompt_cache.py} +1 -1
  64. claude_mpm/services/memory/cache/simple_cache.py +317 -0
  65. claude_mpm/services/{memory_optimizer.py → memory/optimizer.py} +1 -1
  66. claude_mpm/services/{memory_router.py → memory/router.py} +1 -1
  67. claude_mpm/services/optimized_hook_service.py +542 -0
  68. claude_mpm/services/project_registry.py +14 -8
  69. claude_mpm/services/response_tracker.py +237 -0
  70. claude_mpm/services/ticketing_service_original.py +4 -2
  71. claude_mpm/services/version_control/branch_strategy.py +3 -1
  72. claude_mpm/utils/paths.py +12 -10
  73. claude_mpm/utils/session_logging.py +114 -0
  74. claude_mpm/validation/agent_validator.py +2 -1
  75. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/METADATA +26 -20
  76. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/RECORD +83 -106
  77. claude_mpm/cli/commands/ui.py +0 -57
  78. claude_mpm/core/simple_runner.py +0 -1046
  79. claude_mpm/hooks/builtin/__init__.py +0 -1
  80. claude_mpm/hooks/builtin/logging_hook_example.py +0 -165
  81. claude_mpm/hooks/builtin/memory_hooks_example.py +0 -67
  82. claude_mpm/hooks/builtin/mpm_command_hook.py +0 -125
  83. claude_mpm/hooks/builtin/post_delegation_hook_example.py +0 -124
  84. claude_mpm/hooks/builtin/pre_delegation_hook_example.py +0 -125
  85. claude_mpm/hooks/builtin/submit_hook_example.py +0 -100
  86. claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +0 -237
  87. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +0 -240
  88. claude_mpm/hooks/builtin/workflow_start_hook.py +0 -181
  89. claude_mpm/orchestration/__init__.py +0 -6
  90. claude_mpm/orchestration/archive/direct_orchestrator.py +0 -195
  91. claude_mpm/orchestration/archive/factory.py +0 -215
  92. claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +0 -188
  93. claude_mpm/orchestration/archive/hook_integration_example.py +0 -178
  94. claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +0 -826
  95. claude_mpm/orchestration/archive/orchestrator.py +0 -501
  96. claude_mpm/orchestration/archive/pexpect_orchestrator.py +0 -252
  97. claude_mpm/orchestration/archive/pty_orchestrator.py +0 -270
  98. claude_mpm/orchestration/archive/simple_orchestrator.py +0 -82
  99. claude_mpm/orchestration/archive/subprocess_orchestrator.py +0 -801
  100. claude_mpm/orchestration/archive/system_prompt_orchestrator.py +0 -278
  101. claude_mpm/orchestration/archive/wrapper_orchestrator.py +0 -187
  102. claude_mpm/schemas/workflow_validator.py +0 -411
  103. claude_mpm/services/parent_directory_manager/__init__.py +0 -577
  104. claude_mpm/services/parent_directory_manager/backup_manager.py +0 -258
  105. claude_mpm/services/parent_directory_manager/config_manager.py +0 -210
  106. claude_mpm/services/parent_directory_manager/deduplication_manager.py +0 -279
  107. claude_mpm/services/parent_directory_manager/framework_protector.py +0 -143
  108. claude_mpm/services/parent_directory_manager/operations.py +0 -186
  109. claude_mpm/services/parent_directory_manager/state_manager.py +0 -624
  110. claude_mpm/services/parent_directory_manager/template_deployer.py +0 -579
  111. claude_mpm/services/parent_directory_manager/validation_manager.py +0 -378
  112. claude_mpm/services/parent_directory_manager/version_control_helper.py +0 -339
  113. claude_mpm/services/parent_directory_manager/version_manager.py +0 -222
  114. claude_mpm/ui/__init__.py +0 -1
  115. claude_mpm/ui/rich_terminal_ui.py +0 -295
  116. claude_mpm/ui/terminal_ui.py +0 -328
  117. /claude_mpm/services/{agent_versioning.py → agents/deployment/agent_versioning.py} +0 -0
  118. /claude_mpm/services/{agent_capabilities_generator.py → agents/management/agent_capabilities_generator.py} +0 -0
  119. /claude_mpm/services/{agent_persistence_service.py → agents/memory/agent_persistence_service.py} +0 -0
  120. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/WHEEL +0 -0
  121. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/entry_points.txt +0 -0
  122. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/licenses/LICENSE +0 -0
  123. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.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