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.
- 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.0.dist-info}/METADATA +26 -20
- {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/RECORD +83 -106
- claude_mpm/cli/commands/ui.py +0 -57
- claude_mpm/core/simple_runner.py +0 -1046
- claude_mpm/hooks/builtin/__init__.py +0 -1
- claude_mpm/hooks/builtin/logging_hook_example.py +0 -165
- claude_mpm/hooks/builtin/memory_hooks_example.py +0 -67
- claude_mpm/hooks/builtin/mpm_command_hook.py +0 -125
- claude_mpm/hooks/builtin/post_delegation_hook_example.py +0 -124
- claude_mpm/hooks/builtin/pre_delegation_hook_example.py +0 -125
- claude_mpm/hooks/builtin/submit_hook_example.py +0 -100
- claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +0 -237
- claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +0 -240
- claude_mpm/hooks/builtin/workflow_start_hook.py +0 -181
- claude_mpm/orchestration/__init__.py +0 -6
- claude_mpm/orchestration/archive/direct_orchestrator.py +0 -195
- claude_mpm/orchestration/archive/factory.py +0 -215
- claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +0 -188
- claude_mpm/orchestration/archive/hook_integration_example.py +0 -178
- claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +0 -826
- claude_mpm/orchestration/archive/orchestrator.py +0 -501
- claude_mpm/orchestration/archive/pexpect_orchestrator.py +0 -252
- claude_mpm/orchestration/archive/pty_orchestrator.py +0 -270
- claude_mpm/orchestration/archive/simple_orchestrator.py +0 -82
- claude_mpm/orchestration/archive/subprocess_orchestrator.py +0 -801
- claude_mpm/orchestration/archive/system_prompt_orchestrator.py +0 -278
- claude_mpm/orchestration/archive/wrapper_orchestrator.py +0 -187
- claude_mpm/schemas/workflow_validator.py +0 -411
- claude_mpm/services/parent_directory_manager/__init__.py +0 -577
- claude_mpm/services/parent_directory_manager/backup_manager.py +0 -258
- claude_mpm/services/parent_directory_manager/config_manager.py +0 -210
- claude_mpm/services/parent_directory_manager/deduplication_manager.py +0 -279
- claude_mpm/services/parent_directory_manager/framework_protector.py +0 -143
- claude_mpm/services/parent_directory_manager/operations.py +0 -186
- claude_mpm/services/parent_directory_manager/state_manager.py +0 -624
- claude_mpm/services/parent_directory_manager/template_deployer.py +0 -579
- claude_mpm/services/parent_directory_manager/validation_manager.py +0 -378
- claude_mpm/services/parent_directory_manager/version_control_helper.py +0 -339
- claude_mpm/services/parent_directory_manager/version_manager.py +0 -222
- claude_mpm/ui/__init__.py +0 -1
- claude_mpm/ui/rich_terminal_ui.py +0 -295
- claude_mpm/ui/terminal_ui.py +0 -328
- /claude_mpm/services/{agent_versioning.py → agents/deployment/agent_versioning.py} +0 -0
- /claude_mpm/services/{agent_capabilities_generator.py → agents/management/agent_capabilities_generator.py} +0 -0
- /claude_mpm/services/{agent_persistence_service.py → agents/memory/agent_persistence_service.py} +0 -0
- {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/WHEEL +0 -0
- {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.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}")
|