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.

Files changed (159) hide show
  1. claude_mpm/__init__.py +17 -0
  2. claude_mpm/__main__.py +14 -0
  3. claude_mpm/_version.py +32 -0
  4. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +88 -0
  5. claude_mpm/agents/INSTRUCTIONS.md +375 -0
  6. claude_mpm/agents/__init__.py +118 -0
  7. claude_mpm/agents/agent_loader.py +621 -0
  8. claude_mpm/agents/agent_loader_integration.py +229 -0
  9. claude_mpm/agents/agents_metadata.py +204 -0
  10. claude_mpm/agents/base_agent.json +27 -0
  11. claude_mpm/agents/base_agent_loader.py +519 -0
  12. claude_mpm/agents/schema/agent_schema.json +160 -0
  13. claude_mpm/agents/system_agent_config.py +587 -0
  14. claude_mpm/agents/templates/__init__.py +101 -0
  15. claude_mpm/agents/templates/data_engineer_agent.json +46 -0
  16. claude_mpm/agents/templates/documentation_agent.json +45 -0
  17. claude_mpm/agents/templates/engineer_agent.json +49 -0
  18. claude_mpm/agents/templates/ops_agent.json +46 -0
  19. claude_mpm/agents/templates/qa_agent.json +45 -0
  20. claude_mpm/agents/templates/research_agent.json +49 -0
  21. claude_mpm/agents/templates/security_agent.json +46 -0
  22. claude_mpm/agents/templates/update-optimized-specialized-agents.json +374 -0
  23. claude_mpm/agents/templates/version_control_agent.json +46 -0
  24. claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +6 -0
  25. claude_mpm/cli.py +655 -0
  26. claude_mpm/cli_main.py +13 -0
  27. claude_mpm/cli_module/__init__.py +15 -0
  28. claude_mpm/cli_module/args.py +222 -0
  29. claude_mpm/cli_module/commands.py +203 -0
  30. claude_mpm/cli_module/migration_example.py +183 -0
  31. claude_mpm/cli_module/refactoring_guide.md +253 -0
  32. claude_mpm/cli_old/__init__.py +1 -0
  33. claude_mpm/cli_old/ticket_cli.py +102 -0
  34. claude_mpm/config/__init__.py +5 -0
  35. claude_mpm/config/hook_config.py +42 -0
  36. claude_mpm/constants.py +150 -0
  37. claude_mpm/core/__init__.py +45 -0
  38. claude_mpm/core/agent_name_normalizer.py +248 -0
  39. claude_mpm/core/agent_registry.py +627 -0
  40. claude_mpm/core/agent_registry.py.bak +312 -0
  41. claude_mpm/core/agent_session_manager.py +273 -0
  42. claude_mpm/core/base_service.py +747 -0
  43. claude_mpm/core/base_service.py.bak +406 -0
  44. claude_mpm/core/config.py +334 -0
  45. claude_mpm/core/config_aliases.py +292 -0
  46. claude_mpm/core/container.py +347 -0
  47. claude_mpm/core/factories.py +281 -0
  48. claude_mpm/core/framework_loader.py +472 -0
  49. claude_mpm/core/injectable_service.py +206 -0
  50. claude_mpm/core/interfaces.py +539 -0
  51. claude_mpm/core/logger.py +468 -0
  52. claude_mpm/core/minimal_framework_loader.py +107 -0
  53. claude_mpm/core/mixins.py +150 -0
  54. claude_mpm/core/service_registry.py +299 -0
  55. claude_mpm/core/session_manager.py +190 -0
  56. claude_mpm/core/simple_runner.py +511 -0
  57. claude_mpm/core/tool_access_control.py +173 -0
  58. claude_mpm/hooks/README.md +243 -0
  59. claude_mpm/hooks/__init__.py +5 -0
  60. claude_mpm/hooks/base_hook.py +154 -0
  61. claude_mpm/hooks/builtin/__init__.py +1 -0
  62. claude_mpm/hooks/builtin/logging_hook_example.py +165 -0
  63. claude_mpm/hooks/builtin/post_delegation_hook_example.py +124 -0
  64. claude_mpm/hooks/builtin/pre_delegation_hook_example.py +125 -0
  65. claude_mpm/hooks/builtin/submit_hook_example.py +100 -0
  66. claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +237 -0
  67. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +239 -0
  68. claude_mpm/hooks/builtin/workflow_start_hook.py +181 -0
  69. claude_mpm/hooks/hook_client.py +264 -0
  70. claude_mpm/hooks/hook_runner.py +370 -0
  71. claude_mpm/hooks/json_rpc_executor.py +259 -0
  72. claude_mpm/hooks/json_rpc_hook_client.py +319 -0
  73. claude_mpm/hooks/tool_call_interceptor.py +204 -0
  74. claude_mpm/init.py +246 -0
  75. claude_mpm/orchestration/SUBPROCESS_DESIGN.md +66 -0
  76. claude_mpm/orchestration/__init__.py +6 -0
  77. claude_mpm/orchestration/archive/direct_orchestrator.py +195 -0
  78. claude_mpm/orchestration/archive/factory.py +215 -0
  79. claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +188 -0
  80. claude_mpm/orchestration/archive/hook_integration_example.py +178 -0
  81. claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +826 -0
  82. claude_mpm/orchestration/archive/orchestrator.py +501 -0
  83. claude_mpm/orchestration/archive/pexpect_orchestrator.py +252 -0
  84. claude_mpm/orchestration/archive/pty_orchestrator.py +270 -0
  85. claude_mpm/orchestration/archive/simple_orchestrator.py +82 -0
  86. claude_mpm/orchestration/archive/subprocess_orchestrator.py +801 -0
  87. claude_mpm/orchestration/archive/system_prompt_orchestrator.py +278 -0
  88. claude_mpm/orchestration/archive/wrapper_orchestrator.py +187 -0
  89. claude_mpm/scripts/__init__.py +1 -0
  90. claude_mpm/scripts/ticket.py +269 -0
  91. claude_mpm/services/__init__.py +10 -0
  92. claude_mpm/services/agent_deployment.py +955 -0
  93. claude_mpm/services/agent_lifecycle_manager.py +948 -0
  94. claude_mpm/services/agent_management_service.py +596 -0
  95. claude_mpm/services/agent_modification_tracker.py +841 -0
  96. claude_mpm/services/agent_profile_loader.py +606 -0
  97. claude_mpm/services/agent_registry.py +677 -0
  98. claude_mpm/services/base_agent_manager.py +380 -0
  99. claude_mpm/services/framework_agent_loader.py +337 -0
  100. claude_mpm/services/framework_claude_md_generator/README.md +92 -0
  101. claude_mpm/services/framework_claude_md_generator/__init__.py +206 -0
  102. claude_mpm/services/framework_claude_md_generator/content_assembler.py +151 -0
  103. claude_mpm/services/framework_claude_md_generator/content_validator.py +126 -0
  104. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +137 -0
  105. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +106 -0
  106. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +582 -0
  107. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +97 -0
  108. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +27 -0
  109. claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +23 -0
  110. claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +23 -0
  111. claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +20 -0
  112. claude_mpm/services/framework_claude_md_generator/section_generators/header.py +26 -0
  113. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +30 -0
  114. claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +37 -0
  115. claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +111 -0
  116. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +89 -0
  117. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +39 -0
  118. claude_mpm/services/framework_claude_md_generator/section_manager.py +106 -0
  119. claude_mpm/services/framework_claude_md_generator/version_manager.py +121 -0
  120. claude_mpm/services/framework_claude_md_generator.py +621 -0
  121. claude_mpm/services/hook_service.py +388 -0
  122. claude_mpm/services/hook_service_manager.py +223 -0
  123. claude_mpm/services/json_rpc_hook_manager.py +92 -0
  124. claude_mpm/services/parent_directory_manager/README.md +83 -0
  125. claude_mpm/services/parent_directory_manager/__init__.py +577 -0
  126. claude_mpm/services/parent_directory_manager/backup_manager.py +258 -0
  127. claude_mpm/services/parent_directory_manager/config_manager.py +210 -0
  128. claude_mpm/services/parent_directory_manager/deduplication_manager.py +279 -0
  129. claude_mpm/services/parent_directory_manager/framework_protector.py +143 -0
  130. claude_mpm/services/parent_directory_manager/operations.py +186 -0
  131. claude_mpm/services/parent_directory_manager/state_manager.py +624 -0
  132. claude_mpm/services/parent_directory_manager/template_deployer.py +579 -0
  133. claude_mpm/services/parent_directory_manager/validation_manager.py +378 -0
  134. claude_mpm/services/parent_directory_manager/version_control_helper.py +339 -0
  135. claude_mpm/services/parent_directory_manager/version_manager.py +222 -0
  136. claude_mpm/services/shared_prompt_cache.py +819 -0
  137. claude_mpm/services/ticket_manager.py +213 -0
  138. claude_mpm/services/ticket_manager_di.py +318 -0
  139. claude_mpm/services/ticketing_service_original.py +508 -0
  140. claude_mpm/services/version_control/VERSION +1 -0
  141. claude_mpm/services/version_control/__init__.py +70 -0
  142. claude_mpm/services/version_control/branch_strategy.py +670 -0
  143. claude_mpm/services/version_control/conflict_resolution.py +744 -0
  144. claude_mpm/services/version_control/git_operations.py +784 -0
  145. claude_mpm/services/version_control/semantic_versioning.py +703 -0
  146. claude_mpm/ui/__init__.py +1 -0
  147. claude_mpm/ui/rich_terminal_ui.py +295 -0
  148. claude_mpm/ui/terminal_ui.py +328 -0
  149. claude_mpm/utils/__init__.py +16 -0
  150. claude_mpm/utils/config_manager.py +468 -0
  151. claude_mpm/utils/import_migration_example.py +80 -0
  152. claude_mpm/utils/imports.py +182 -0
  153. claude_mpm/utils/path_operations.py +357 -0
  154. claude_mpm/utils/paths.py +289 -0
  155. claude_mpm-0.3.0.dist-info/METADATA +290 -0
  156. claude_mpm-0.3.0.dist-info/RECORD +159 -0
  157. claude_mpm-0.3.0.dist-info/WHEEL +5 -0
  158. claude_mpm-0.3.0.dist-info/entry_points.txt +4 -0
  159. claude_mpm-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,378 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Validation Manager - Handles validation and compatibility checks
4
+ ================================================================================
5
+
6
+ This module manages validation of parent directories, subsystem compatibility,
7
+ and version comparisons.
8
+ """
9
+
10
+ import os
11
+ from pathlib import Path
12
+ from typing import Dict, Any, Optional, List, Tuple
13
+ from datetime import datetime
14
+ import logging
15
+
16
+ from .state_manager import ParentDirectoryOperation, ParentDirectoryAction
17
+ from ...utils.path_operations import path_ops
18
+
19
+
20
+ class ValidationManager:
21
+ """Manages validation and compatibility checks."""
22
+
23
+ def __init__(self, logger: Optional[logging.Logger] = None):
24
+ """
25
+ Initialize the Validation Manager.
26
+
27
+ Args:
28
+ logger: Logger instance to use
29
+ """
30
+ self.logger = logger or logging.getLogger(__name__)
31
+
32
+ async def validate_parent_directory(
33
+ self,
34
+ target_directory: Path,
35
+ managed_directories: Dict[str, Any],
36
+ template_manager: Optional[Any] = None
37
+ ) -> ParentDirectoryOperation:
38
+ """
39
+ Validate a parent directory's template.
40
+
41
+ Args:
42
+ target_directory: Directory to validate
43
+ managed_directories: Dictionary of managed directories
44
+ template_manager: Template manager instance (optional)
45
+
46
+ Returns:
47
+ ParentDirectoryOperation result
48
+ """
49
+ try:
50
+ # Check for INSTRUCTIONS.md first, then CLAUDE.md
51
+ target_file = target_directory / "INSTRUCTIONS.md"
52
+ if not path_ops.validate_exists(target_file):
53
+ target_file = target_directory / "CLAUDE.md"
54
+
55
+ if not path_ops.validate_exists(target_file):
56
+ return ParentDirectoryOperation(
57
+ action=ParentDirectoryAction.VALIDATE,
58
+ target_path=target_file,
59
+ success=False,
60
+ error_message="INSTRUCTIONS.md/CLAUDE.md file not found",
61
+ )
62
+
63
+ # Check if managed
64
+ directory_key = str(target_directory)
65
+ if directory_key not in managed_directories:
66
+ return ParentDirectoryOperation(
67
+ action=ParentDirectoryAction.VALIDATE,
68
+ target_path=target_file,
69
+ success=True,
70
+ warnings=["Directory not managed by Parent Directory Manager"],
71
+ )
72
+
73
+ config = managed_directories[directory_key]
74
+
75
+ # Validate template if template manager available
76
+ validation_errors = []
77
+ validation_warnings = []
78
+
79
+ # template_manager removed - use Claude Code Task Tool instead
80
+ rendered_content = None
81
+
82
+ if rendered_content:
83
+ # Compare with actual content
84
+ actual_content = path_ops.safe_read(target_file)
85
+ if not actual_content:
86
+ return ParentDirectoryOperation(
87
+ action=ParentDirectoryAction.VALIDATE,
88
+ target_path=target_file,
89
+ success=False,
90
+ error_message="Failed to read file content",
91
+ )
92
+
93
+ if actual_content != rendered_content:
94
+ validation_warnings.append("Content differs from expected template output")
95
+ else:
96
+ validation_errors.append("Failed to render template for validation")
97
+
98
+ # Check file permissions
99
+ if not os.access(target_file, os.R_OK):
100
+ validation_errors.append("File is not readable")
101
+
102
+ if not os.access(target_file, os.W_OK):
103
+ validation_warnings.append("File is not writable")
104
+
105
+ # Create operation result
106
+ operation = ParentDirectoryOperation(
107
+ action=ParentDirectoryAction.VALIDATE,
108
+ target_path=target_file,
109
+ success=len(validation_errors) == 0,
110
+ template_id=config.template_id,
111
+ error_message="; ".join(validation_errors) if validation_errors else None,
112
+ warnings=validation_warnings,
113
+ )
114
+
115
+ return operation
116
+
117
+ except Exception as e:
118
+ self.logger.error(f"Failed to validate parent directory {target_directory}: {e}")
119
+ return ParentDirectoryOperation(
120
+ action=ParentDirectoryAction.VALIDATE,
121
+ # Try to determine which file would be used
122
+ target_file = target_directory / "INSTRUCTIONS.md"
123
+ if not path_ops.validate_exists(target_file):
124
+ target_file = target_directory / "CLAUDE.md"
125
+ return ParentDirectoryOperation(
126
+ action=ParentDirectoryAction.VALIDATE,
127
+ target_path=target_file,
128
+ success=False,
129
+ success=False,
130
+ error_message=str(e),
131
+ )
132
+
133
+ async def validate_subsystem_compatibility(
134
+ self,
135
+ required_versions: Dict[str, str],
136
+ get_subsystem_version_func
137
+ ) -> Dict[str, Any]:
138
+ """
139
+ Validate subsystem version compatibility against requirements.
140
+
141
+ Args:
142
+ required_versions: Dictionary of subsystem -> required version
143
+ get_subsystem_version_func: Function to get current subsystem version
144
+
145
+ Returns:
146
+ Validation results with compatibility status
147
+ """
148
+ try:
149
+ results = {
150
+ "compatible": True,
151
+ "validation_timestamp": datetime.now().isoformat(),
152
+ "subsystem_checks": {}
153
+ }
154
+
155
+ for subsystem, required_version in required_versions.items():
156
+ current_version = get_subsystem_version_func(subsystem)
157
+
158
+ check_result = {
159
+ "subsystem": subsystem,
160
+ "required_version": required_version,
161
+ "current_version": current_version,
162
+ "compatible": False,
163
+ "status": "unknown"
164
+ }
165
+
166
+ if current_version is None or current_version in ["unknown", "not_found"]:
167
+ check_result["status"] = "missing"
168
+ results["compatible"] = False
169
+ elif current_version == required_version:
170
+ check_result["compatible"] = True
171
+ check_result["status"] = "exact_match"
172
+ else:
173
+ # Try version comparison for compatibility
174
+ try:
175
+ comparison = self.compare_subsystem_versions(current_version, required_version)
176
+ if comparison >= 0:
177
+ check_result["compatible"] = True
178
+ check_result["status"] = "compatible" if comparison > 0 else "exact_match"
179
+ else:
180
+ check_result["status"] = "outdated"
181
+ results["compatible"] = False
182
+ except Exception as comp_error:
183
+ check_result["status"] = "comparison_failed"
184
+ check_result["error"] = str(comp_error)
185
+ results["compatible"] = False
186
+
187
+ results["subsystem_checks"][subsystem] = check_result
188
+
189
+ return results
190
+
191
+ except Exception as e:
192
+ self.logger.error(f"Failed to validate subsystem compatibility: {e}")
193
+ return {
194
+ "compatible": False,
195
+ "error": str(e),
196
+ "validation_timestamp": datetime.now().isoformat()
197
+ }
198
+
199
+ def compare_subsystem_versions(self, version1: str, version2: str) -> int:
200
+ """
201
+ Compare two subsystem version strings.
202
+ Supports serial number format (001, 002, etc.).
203
+
204
+ Args:
205
+ version1: First version string
206
+ version2: Second version string
207
+
208
+ Returns:
209
+ -1 if version1 < version2
210
+ 0 if version1 == version2
211
+ 1 if version1 > version2
212
+ """
213
+ try:
214
+ # Handle serial number format (001, 002, etc.)
215
+ if version1.isdigit() and version2.isdigit():
216
+ v1_num = int(version1)
217
+ v2_num = int(version2)
218
+ if v1_num < v2_num:
219
+ return -1
220
+ elif v1_num > v2_num:
221
+ return 1
222
+ else:
223
+ return 0
224
+
225
+ # Handle semantic versioning (x.y.z)
226
+ if "." in version1 and "." in version2:
227
+ v1_parts = [int(x) for x in version1.split(".")]
228
+ v2_parts = [int(x) for x in version2.split(".")]
229
+
230
+ # Pad shorter version with zeros
231
+ max_len = max(len(v1_parts), len(v2_parts))
232
+ v1_parts.extend([0] * (max_len - len(v1_parts)))
233
+ v2_parts.extend([0] * (max_len - len(v2_parts)))
234
+
235
+ for i in range(max_len):
236
+ if v1_parts[i] < v2_parts[i]:
237
+ return -1
238
+ elif v1_parts[i] > v2_parts[i]:
239
+ return 1
240
+
241
+ return 0
242
+
243
+ # String comparison fallback
244
+ if version1 < version2:
245
+ return -1
246
+ elif version1 > version2:
247
+ return 1
248
+ else:
249
+ return 0
250
+
251
+ except Exception as e:
252
+ self.logger.error(f"Failed to compare subsystem versions {version1} vs {version2}: {e}")
253
+ # If comparison fails, assume versions are different
254
+ return -1 if version1 != version2 else 0
255
+
256
+ def validate_framework_template_integrity(self, framework_path: Path) -> bool:
257
+ """
258
+ Validate that the framework template exists and has expected content.
259
+
260
+ Args:
261
+ framework_path: Path to framework
262
+
263
+ Returns:
264
+ True if framework template is valid, False otherwise
265
+ """
266
+ try:
267
+ # Try INSTRUCTIONS.md first, then fall back to CLAUDE.md
268
+ framework_template_path = framework_path / "agents" / "INSTRUCTIONS.md"
269
+ if not path_ops.validate_exists(framework_template_path):
270
+ framework_template_path = framework_path / "agents" / "CLAUDE.md"
271
+
272
+ if not path_ops.validate_exists(framework_template_path):
273
+ self.logger.error(f"Framework template does not exist: {framework_template_path}")
274
+ return False
275
+
276
+ if not path_ops.validate_is_file(framework_template_path):
277
+ self.logger.error(f"Framework template path is not a file: {framework_template_path}")
278
+ return False
279
+
280
+ # Read and validate content
281
+ content = path_ops.safe_read(framework_template_path)
282
+ if not content:
283
+ self.logger.error(f"Failed to read framework template at {framework_template_path}")
284
+ return False
285
+
286
+ if len(content.strip()) == 0:
287
+ self.logger.error(f"Framework template is empty: {framework_template_path}")
288
+ return False
289
+
290
+ # Check for critical content markers
291
+ critical_markers = [
292
+ "AI ASSISTANT ROLE DESIGNATION",
293
+ "CLAUDE_MD_VERSION:",
294
+ "Framework Context"
295
+ ]
296
+
297
+ missing_critical = [marker for marker in critical_markers if marker not in content]
298
+
299
+ if missing_critical:
300
+ self.logger.error(f"Framework template missing critical content: {missing_critical}")
301
+ return False
302
+
303
+ self.logger.debug(f"Framework template integrity verified: {framework_template_path}")
304
+ return True
305
+
306
+ except Exception as e:
307
+ self.logger.error(f"Failed to validate framework template integrity: {e}")
308
+ return False
309
+
310
+ async def validate_deployment_context(
311
+ self,
312
+ deployment_aware: bool,
313
+ dependency_manager: Optional[Any] = None
314
+ ) -> Optional[Dict[str, Any]]:
315
+ """
316
+ Validate deployment context using CMPM-101.
317
+
318
+ Args:
319
+ deployment_aware: Whether to check deployment context
320
+ dependency_manager: Dependency manager instance
321
+
322
+ Returns:
323
+ Deployment context or None
324
+ """
325
+ try:
326
+ if not deployment_aware:
327
+ return None
328
+
329
+ # Use dependency manager to get deployment context
330
+ # dependency_manager removed - use Claude Code Task Tool instead
331
+ deployment_config = None
332
+ if deployment_config:
333
+ self.logger.info(
334
+ f"Deployment context validated: {deployment_config.get('strategy', 'unknown')}"
335
+ )
336
+ return deployment_config
337
+ else:
338
+ self.logger.warning("No deployment context available - dependency manager removed")
339
+ return None
340
+
341
+ except Exception as e:
342
+ self.logger.error(f"Failed to validate deployment context: {e}")
343
+ return None
344
+
345
+ def should_skip_deployment(
346
+ self,
347
+ target_file: Path,
348
+ template_content: str,
349
+ force: bool,
350
+ template_deployer
351
+ ) -> Tuple[bool, Optional[str], bool]:
352
+ """
353
+ Check if deployment should be skipped based on file type and version comparison.
354
+
355
+ Args:
356
+ target_file: Target file path
357
+ template_content: Template content to deploy
358
+ force: Force deployment flag
359
+ template_deployer: Template deployer instance
360
+
361
+ Returns:
362
+ Tuple of (should_skip, reason, is_permanent_protection)
363
+ """
364
+ should_skip, reason = template_deployer.should_skip_deployment(
365
+ target_file, template_content, force
366
+ )
367
+
368
+ # Check for permanent protection
369
+ is_permanent_protection = False
370
+ if path_ops.validate_exists(target_file) and should_skip:
371
+ existing_content = path_ops.safe_read(target_file)
372
+ if not existing_content:
373
+ return False, "Failed to read existing file content"
374
+ if not template_deployer.is_framework_deployment_template(existing_content):
375
+ is_permanent_protection = True
376
+ reason = "Existing file is not a framework deployment template"
377
+
378
+ return should_skip, reason, is_permanent_protection