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.
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.1.dist-info}/METADATA +28 -20
  76. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.1.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.1.dist-info}/WHEEL +0 -0
  121. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.1.dist-info}/entry_points.txt +0 -0
  122. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.1.dist-info}/licenses/LICENSE +0 -0
  123. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.1.dist-info}/top_level.txt +0 -0
@@ -1,624 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- State Manager - Handles initialization, cleanup, and operation history
4
- ================================================================================
5
-
6
- This module manages the initialization, cleanup, and operation tracking
7
- for the parent directory manager service.
8
- """
9
-
10
- import os
11
- from pathlib import Path
12
- from typing import Dict, Any, List, Optional
13
- from dataclasses import dataclass, field
14
- from datetime import datetime
15
- from enum import Enum
16
- import logging
17
- import json
18
-
19
- from ...core.logger import setup_logging, setup_streaming_logger, finalize_streaming_logs
20
- from ...utils.path_operations import path_ops
21
-
22
-
23
- class ParentDirectoryAction(Enum):
24
- """Actions for parent directory operations."""
25
- INSTALL = "install"
26
- UPDATE = "update"
27
- BACKUP = "backup"
28
- RESTORE = "restore"
29
- VALIDATE = "validate"
30
- REMOVE = "remove"
31
-
32
-
33
- @dataclass
34
- class ParentDirectoryStatus:
35
- """Status information for parent directory files."""
36
- file_path: Path
37
- exists: bool
38
- is_managed: bool
39
- current_version: Optional[str] = None
40
- last_modified: Optional[datetime] = None
41
- checksum: Optional[str] = None
42
- backup_available: bool = False
43
- template_source: Optional[str] = None
44
- deployment_context: Optional[str] = None
45
-
46
-
47
- @dataclass
48
- class ParentDirectoryOperation:
49
- """Result of a parent directory operation."""
50
- action: ParentDirectoryAction
51
- target_path: Path
52
- success: bool
53
- template_id: Optional[str] = None
54
- version: Optional[str] = None
55
- backup_path: Optional[Path] = None
56
- error_message: Optional[str] = None
57
- changes_made: List[str] = field(default_factory=list)
58
- warnings: List[str] = field(default_factory=list)
59
-
60
-
61
- class StateManager:
62
- """Manages state and lifecycle for parent directory operations."""
63
-
64
- def __init__(
65
- self,
66
- working_dir: Path,
67
- parent_directory_manager_dir: Path,
68
- framework_path: Path,
69
- quiet_mode: bool = False,
70
- logger: Optional[logging.Logger] = None
71
- ):
72
- """
73
- Initialize the State Manager.
74
-
75
- Args:
76
- working_dir: Current working directory
77
- parent_directory_manager_dir: Parent directory manager directory
78
- framework_path: Path to framework
79
- quiet_mode: If True, suppress INFO level logging
80
- logger: Logger instance to use
81
- """
82
- self.working_dir = working_dir
83
- self.parent_directory_manager_dir = parent_directory_manager_dir
84
- self.framework_path = framework_path
85
- self.quiet_mode = quiet_mode
86
- self.logger = logger
87
- self._startup_phase = True
88
-
89
- # Operation history tracking
90
- self.operation_history: List[ParentDirectoryOperation] = []
91
- self.logs_dir = parent_directory_manager_dir / "logs"
92
- self.operation_history_file = self.logs_dir / "operation_history.json"
93
-
94
- # Subsystem version tracking
95
- self.subsystem_versions = {}
96
-
97
- # Deployment context
98
- self.deployment_context = None
99
-
100
- def log_info_if_not_quiet(self, message: str) -> None:
101
- """Log INFO message only if not in quiet mode."""
102
- if not self.quiet_mode and self.logger:
103
- self.logger.info(message)
104
-
105
- def log_protection_guidance(self, target_file: Path, skip_reason: str) -> None:
106
- """
107
- Log detailed guidance when permanent protection blocks deployment.
108
-
109
- Args:
110
- target_file: The file that's being protected
111
- skip_reason: The reason deployment was blocked
112
- """
113
- if not self.logger:
114
- return
115
-
116
- self.logger.error("")
117
- self.logger.error("🚫 DEPLOYMENT BLOCKED BY PERMANENT PROTECTION")
118
- self.logger.error("=" * 50)
119
- self.logger.error(f"Target file: {target_file}")
120
- self.logger.error(f"Protection reason: {skip_reason}")
121
- self.logger.error("")
122
- self.logger.error("📋 EXPLANATION:")
123
- self.logger.error("The file you're trying to deploy to is NOT a framework deployment template.")
124
- self.logger.error("This protection prevents overwriting project development files and custom CLAUDE.md files.")
125
- self.logger.error("")
126
- self.logger.error("✅ WHAT CAN BE REPLACED:")
127
- self.logger.error("• Framework deployment templates (identified by specific title and metadata)")
128
- self.logger.error("• Files with title: '# Claude PM Framework Configuration - Deployment'")
129
- self.logger.error("• Files containing framework deployment metadata blocks")
130
- self.logger.error("")
131
- self.logger.error("🛡️ WHAT IS PROTECTED:")
132
- self.logger.error("• Project development files")
133
- self.logger.error("• Custom CLAUDE.md files")
134
- self.logger.error("• Any file not matching framework deployment template pattern")
135
- self.logger.error("")
136
- self.logger.error("🔧 RESOLUTION OPTIONS:")
137
- self.logger.error("1. If this is a project development file, keep it as-is (protection working correctly)")
138
- self.logger.error("2. If you need framework deployment here, manually remove the file first")
139
- self.logger.error("3. If you need both, rename the existing file to preserve your work")
140
- self.logger.error("")
141
- self.logger.error("⚠️ IMPORTANT:")
142
- self.logger.error("The --force flag CANNOT override this protection by design.")
143
- self.logger.error("This ensures your project development files are never accidentally overwritten.")
144
- self.logger.error("=" * 50)
145
-
146
- def handle_protection_error(self, target_path: Path, error_message: str, simple: bool = False) -> None:
147
- """Handle protection error with appropriate logging.
148
-
149
- Args:
150
- target_path: The path that was protected
151
- error_message: The error message
152
- simple: If True, use simple guidance (just call log_protection_guidance)
153
- """
154
- if simple:
155
- self.log_protection_guidance(target_path, error_message)
156
- else:
157
- self.log_info_if_not_quiet(f"🚫 Force flag cannot override project development file protection")
158
- self.log_info_if_not_quiet(f" • This protection prevents overwriting non-framework files")
159
- self.log_info_if_not_quiet(f" • Only framework deployment templates can be replaced")
160
- self.log_protection_guidance(target_path, error_message)
161
-
162
- def create_directory_structure(self) -> None:
163
- """Create the parent directory manager directory structure."""
164
- directories = [
165
- self.parent_directory_manager_dir,
166
- self.parent_directory_manager_dir / "backups",
167
- self.parent_directory_manager_dir / "configs",
168
- self.parent_directory_manager_dir / "versions",
169
- self.logs_dir,
170
- self.working_dir / ".claude-pm" / "backups" / "framework",
171
- ]
172
-
173
- for directory in directories:
174
- path_ops.ensure_dir(directory)
175
-
176
- def initialize_paths(self, parent_directory_manager_dir: Path, working_dir: Path) -> Dict[str, Path]:
177
- """Initialize and return parent directory manager paths.
178
-
179
- Args:
180
- parent_directory_manager_dir: Parent directory manager directory
181
- working_dir: Working directory
182
-
183
- Returns:
184
- Dictionary of initialized paths
185
- """
186
- return {
187
- 'backups_dir': working_dir / ".claude-pm" / "backups" / "parent_directory_manager",
188
- 'configs_dir': parent_directory_manager_dir / "configs",
189
- 'versions_dir': parent_directory_manager_dir / "versions",
190
- 'logs_dir': parent_directory_manager_dir / "logs",
191
- 'managed_directories_file': parent_directory_manager_dir / "configs" / "managed_directories.json",
192
- 'operation_history_file': parent_directory_manager_dir / "logs" / "operation_history.json"
193
- }
194
-
195
- async def cleanup_temporary_files(self, backups_dir: Path, retention_days: int) -> None:
196
- """
197
- Cleanup temporary files and old backups.
198
-
199
- Args:
200
- backups_dir: Directory containing backups
201
- retention_days: Number of days to retain backups
202
- """
203
- try:
204
- # Clean up old backups
205
- if path_ops.validate_is_dir(backups_dir):
206
- cutoff_date = datetime.now().timestamp() - (
207
- retention_days * 24 * 60 * 60
208
- )
209
-
210
- for backup_file in backups_dir.rglob("*"):
211
- if backup_file.is_file():
212
- file_mtime = backup_file.stat().st_mtime
213
- if file_mtime < cutoff_date:
214
- path_ops.safe_delete(backup_file)
215
- if self.logger:
216
- self.logger.debug(f"Removed old backup: {backup_file}")
217
-
218
- except Exception as e:
219
- if self.logger:
220
- self.logger.error(f"Failed to cleanup temporary files: {e}")
221
-
222
- def add_operation(self, operation: ParentDirectoryOperation) -> None:
223
- """
224
- Add an operation to the history.
225
-
226
- Args:
227
- operation: Operation to add
228
- """
229
- self.operation_history.append(operation)
230
-
231
- def create_operation_result(
232
- self,
233
- action: ParentDirectoryAction,
234
- target_path: Path,
235
- success: bool,
236
- template_id: Optional[str] = None,
237
- version: Optional[str] = None,
238
- backup_manager = None,
239
- changes_made: List[str] = None,
240
- warnings: List[str] = None,
241
- error_message: Optional[str] = None
242
- ) -> ParentDirectoryOperation:
243
- """Create a ParentDirectoryOperation result with common logic.
244
-
245
- Args:
246
- action: Action type
247
- target_path: Target path
248
- success: Success status
249
- template_id: Template ID
250
- version: Version
251
- backup_manager: Backup manager instance
252
- changes_made: List of changes made
253
- warnings: List of warnings
254
- error_message: Error message if failed
255
-
256
- Returns:
257
- ParentDirectoryOperation instance
258
- """
259
- backup_path = None
260
- if success and backup_manager and hasattr(backup_manager, 'last_backup_path'):
261
- backup_path = backup_manager.last_backup_path
262
-
263
- return ParentDirectoryOperation(
264
- action=action,
265
- target_path=target_path,
266
- success=success,
267
- template_id=template_id,
268
- version=version,
269
- backup_path=backup_path,
270
- changes_made=changes_made or [],
271
- warnings=warnings or [],
272
- error_message=error_message
273
- )
274
-
275
- async def get_operation_history(self, limit: int = 50) -> List[Dict[str, Any]]:
276
- """
277
- Get operation history.
278
-
279
- Args:
280
- limit: Maximum number of operations to return
281
-
282
- Returns:
283
- List of operation history entries
284
- """
285
- try:
286
- history = []
287
-
288
- # Get most recent operations
289
- recent_operations = (
290
- self.operation_history[-limit:] if limit > 0 else self.operation_history
291
- )
292
-
293
- for operation in recent_operations:
294
- history_entry = {
295
- "action": operation.action.value,
296
- "target_path": str(operation.target_path),
297
- "success": operation.success,
298
- "template_id": operation.template_id,
299
- "version": operation.version,
300
- "backup_path": str(operation.backup_path) if operation.backup_path else None,
301
- "error_message": operation.error_message,
302
- "changes_made": operation.changes_made,
303
- "warnings": operation.warnings,
304
- }
305
-
306
- history.append(history_entry)
307
-
308
- return history
309
-
310
- except Exception as e:
311
- if self.logger:
312
- self.logger.error(f"Failed to get operation history: {e}")
313
- return []
314
-
315
- def detect_framework_path(self) -> Path:
316
- """Detect framework path from environment or deployment structure."""
317
- # Try environment variable first (set by Node.js CLI)
318
- if framework_path := os.getenv('CLAUDE_PM_FRAMEWORK_PATH'):
319
- return Path(framework_path)
320
-
321
- # Try deployment directory
322
- if deployment_dir := os.getenv('CLAUDE_PM_DEPLOYMENT_DIR'):
323
- return Path(deployment_dir)
324
-
325
- # Check if we're running from a wheel/installed package
326
- current_path = Path(__file__).resolve()
327
- path_str = str(current_path)
328
- if 'site-packages' in path_str or 'dist-packages' in path_str:
329
- # For wheel installations, framework files are in the data directory
330
- package_dir = Path(__file__).parent.parent.parent.parent
331
- # Check in the package's data directory
332
- data_framework_path = package_dir / 'data' / 'framework' / 'CLAUDE.md'
333
- if path_ops.validate_exists(data_framework_path):
334
- return package_dir / 'data'
335
- # Also check for legacy location within the package
336
- legacy_framework_path = package_dir / 'framework' / 'CLAUDE.md'
337
- if path_ops.validate_exists(legacy_framework_path):
338
- return package_dir
339
-
340
- # Try relative to current module (source installations)
341
- current_dir = Path(__file__).parent.parent.parent.parent
342
- if path_ops.validate_exists(current_dir / 'framework' / 'CLAUDE.md'):
343
- return current_dir
344
-
345
- # Fallback to working directory
346
- return self.working_dir
347
-
348
- def finalize_startup(self) -> None:
349
- """Finalize startup phase and switch to normal logging."""
350
- if self.logger:
351
- finalize_streaming_logs(self.logger)
352
- self.logger = setup_logging(self.logger.name)
353
- self._startup_phase = False
354
-
355
- def is_startup_phase(self) -> bool:
356
- """Check if in startup phase."""
357
- return self._startup_phase
358
-
359
- def setup_deployment_logger(self, logger):
360
- """Setup streaming logger for deployment if in startup phase.
361
-
362
- Args:
363
- logger: Current logger instance
364
-
365
- Returns:
366
- Tuple of (original_logger, deployment_streaming)
367
- """
368
- if self.is_startup_phase():
369
- original_logger = logger
370
- return setup_streaming_logger(logger.name), original_logger, True
371
- return logger, None, False
372
-
373
- def finalize_deployment_logger(self, logger, original_logger, deployment_streaming):
374
- """Finalize streaming logs if we used streaming logger.
375
-
376
- Args:
377
- logger: Current logger (possibly streaming)
378
- original_logger: Original logger to restore
379
- deployment_streaming: Whether streaming was used
380
-
381
- Returns:
382
- Restored logger
383
- """
384
- if deployment_streaming and original_logger:
385
- finalize_streaming_logs(logger)
386
- return original_logger
387
- return logger
388
-
389
- async def get_parent_directory_status(
390
- self,
391
- target_directory: Path,
392
- config_manager,
393
- backups_dir: Path
394
- ) -> ParentDirectoryStatus:
395
- """
396
- Get status of a parent directory.
397
-
398
- Args:
399
- target_directory: Directory to check
400
- config_manager: Configuration manager instance
401
- backups_dir: Directory containing backups
402
-
403
- Returns:
404
- ParentDirectoryStatus object
405
- """
406
- try:
407
- # Ensure target_directory is a Path object
408
- target_directory = Path(target_directory)
409
- target_file = target_directory / "CLAUDE.md"
410
-
411
- # Check if file exists
412
- if not path_ops.validate_exists(target_file):
413
- return ParentDirectoryStatus(
414
- file_path=target_file,
415
- exists=False,
416
- is_managed=config_manager.is_directory_managed(str(target_directory)),
417
- )
418
-
419
- # Get file information
420
- stat = target_file.stat()
421
- last_modified = datetime.fromtimestamp(stat.st_mtime)
422
-
423
- # Calculate checksum
424
- import hashlib
425
- content = path_ops.safe_read(target_file)
426
- if not content:
427
- return ParentDirectoryStatus(
428
- file_path=target_file,
429
- exists=False,
430
- is_managed=config_manager.is_directory_managed(str(target_directory)),
431
- )
432
- checksum = hashlib.sha256(content.encode()).hexdigest()
433
-
434
- # Check if managed
435
- directory_key = str(target_directory)
436
- is_managed = config_manager.is_directory_managed(directory_key)
437
-
438
- # Get template source if managed
439
- template_source = None
440
- current_version = None
441
- deployment_context = None
442
-
443
- if is_managed:
444
- config = config_manager.get_directory_config(directory_key)
445
- template_source = config.template_id
446
-
447
- # Get template version
448
- # template_manager removed - use Claude Code Task Tool instead
449
- current_version = "unknown"
450
-
451
- deployment_context = config.deployment_metadata.get("deployment_type")
452
-
453
- # Check for backups
454
- backup_available = False
455
- if path_ops.validate_is_dir(backups_dir):
456
- backup_pattern = f"*{target_file.name}*"
457
- backup_files = list(backups_dir.glob(backup_pattern))
458
- backup_available = len(backup_files) > 0
459
-
460
- return ParentDirectoryStatus(
461
- file_path=target_file,
462
- exists=True,
463
- is_managed=is_managed,
464
- current_version=current_version,
465
- last_modified=last_modified,
466
- checksum=checksum,
467
- backup_available=backup_available,
468
- template_source=template_source,
469
- deployment_context=deployment_context,
470
- )
471
-
472
- except Exception as e:
473
- if self.logger:
474
- self.logger.error(f"Failed to get parent directory status for {target_directory}: {e}")
475
- return ParentDirectoryStatus(
476
- file_path=Path(target_directory) / "CLAUDE.md", exists=False, is_managed=False
477
- )
478
-
479
- async def list_managed_directories(
480
- self,
481
- config_manager,
482
- get_parent_directory_status_func
483
- ) -> List[Dict[str, Any]]:
484
- """
485
- List all managed directories.
486
-
487
- Args:
488
- config_manager: Configuration manager instance
489
- get_parent_directory_status_func: Function to get directory status
490
-
491
- Returns:
492
- List of managed directory information
493
- """
494
- try:
495
- directories = []
496
-
497
- for directory_key, config in config_manager.get_all_managed_directories().items():
498
- # Get current status
499
- status = await get_parent_directory_status_func(config.target_directory)
500
-
501
- directory_info = {
502
- "directory": str(config.target_directory),
503
- "context": config.context.value,
504
- "template_id": config.template_id,
505
- "exists": status.exists,
506
- "is_managed": status.is_managed,
507
- "current_version": status.current_version,
508
- "last_modified": (
509
- status.last_modified.isoformat() if status.last_modified else None
510
- ),
511
- "backup_available": status.backup_available,
512
- "deployment_context": status.deployment_context,
513
- }
514
-
515
- directories.append(directory_info)
516
-
517
- return directories
518
-
519
- except Exception as e:
520
- if self.logger:
521
- self.logger.error(f"Failed to list managed directories: {e}")
522
- return []
523
-
524
- async def initialize(
525
- self,
526
- create_directory_structure_func,
527
- config_manager,
528
- validation_manager,
529
- version_manager,
530
- deduplicate_claude_md_files_func,
531
- deployment_aware: bool,
532
- dependency_manager: Optional[Any]
533
- ) -> None:
534
- """
535
- Initialize the Parent Directory Manager service.
536
-
537
- Args:
538
- create_directory_structure_func: Function to create directories
539
- config_manager: Configuration manager instance
540
- validation_manager: Validation manager instance
541
- version_manager: Version manager instance
542
- deduplicate_claude_md_files_func: Deduplication function
543
- deployment_aware: Whether to check deployment context
544
- dependency_manager: Dependency manager instance
545
- """
546
- self.log_info_if_not_quiet("Initializing Parent Directory Manager...")
547
-
548
- try:
549
- # Create directory structure
550
- create_directory_structure_func()
551
-
552
- # Load existing configurations
553
- await config_manager.load_managed_directories()
554
-
555
- # Validate deployment context
556
- self.deployment_context = await validation_manager.validate_deployment_context(
557
- deployment_aware,
558
- dependency_manager
559
- )
560
-
561
- # Validate framework template integrity on startup
562
- if not validation_manager.validate_framework_template_integrity(self.framework_path):
563
- if self.logger:
564
- self.logger.warning("Framework template integrity check failed during initialization")
565
-
566
- # Load subsystem versions
567
- await version_manager.load_subsystem_versions()
568
-
569
- # CRITICAL STARTUP TASK: Run deduplication to prevent duplicate context loading
570
- self.log_info_if_not_quiet("Running CLAUDE.md deduplication on startup...")
571
- try:
572
- dedup_actions = await deduplicate_claude_md_files_func()
573
- if dedup_actions:
574
- backed_up_count = sum(1 for _, action in dedup_actions if "backed up" in action)
575
- if backed_up_count > 0:
576
- # Always log deduplication results, even in quiet mode
577
- if self.logger:
578
- self.logger.warning(f"🧹 Deduplication cleaned up {backed_up_count} redundant framework CLAUDE.md templates")
579
- for path, action in dedup_actions:
580
- if "backed up" in action:
581
- self.logger.warning(f" - {path} → {action}")
582
- except Exception as e:
583
- # Always log deduplication errors
584
- if self.logger:
585
- self.logger.error(f"Failed to run CLAUDE.md deduplication on startup: {e}")
586
-
587
- self.log_info_if_not_quiet("Parent Directory Manager initialized successfully")
588
-
589
- # Finalize streaming logs after initialization
590
- self.finalize_startup()
591
-
592
- except Exception as e:
593
- if self.logger:
594
- self.logger.error(f"Failed to initialize Parent Directory Manager: {e}")
595
- raise
596
-
597
- async def cleanup(
598
- self,
599
- config_manager,
600
- backups_dir: Path,
601
- retention_days: int
602
- ) -> None:
603
- """
604
- Cleanup the Parent Directory Manager service.
605
-
606
- Args:
607
- config_manager: Configuration manager instance
608
- backups_dir: Directory containing backups
609
- retention_days: Number of days to retain backups
610
- """
611
- self.log_info_if_not_quiet("Cleaning up Parent Directory Manager...")
612
-
613
- try:
614
- # Save current state
615
- await config_manager.save_managed_directories()
616
-
617
- # Cleanup temporary files
618
- await self.cleanup_temporary_files(backups_dir, retention_days)
619
-
620
- self.log_info_if_not_quiet("Parent Directory Manager cleanup completed")
621
-
622
- except Exception as e:
623
- if self.logger:
624
- self.logger.error(f"Failed to cleanup Parent Directory Manager: {e}")