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,841 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Agent Modification Tracker - Consolidated Service
4
+ =================================================
5
+
6
+ Comprehensive agent modification tracking and persistence system for monitoring
7
+ agent changes across the three-tier hierarchy with real-time detection and
8
+ intelligent persistence management.
9
+
10
+ Key Features:
11
+ - Real-time file system monitoring for agent changes
12
+ - Modification history and version tracking
13
+ - Agent backup and restore functionality
14
+ - Modification validation and conflict detection
15
+ - SharedPromptCache invalidation integration
16
+ - Persistence storage in hierarchy-appropriate locations
17
+ - Change classification (create, modify, delete, move)
18
+
19
+ This is a consolidated version of the agent_modification_tracker service,
20
+ combining all functionality into a single module for better maintainability.
21
+ """
22
+
23
+ import asyncio
24
+ import hashlib
25
+ import json
26
+ import logging
27
+ import os
28
+ import shutil
29
+ import time
30
+ import uuid
31
+ from dataclasses import dataclass, field, asdict
32
+ from datetime import datetime
33
+ from enum import Enum
34
+ from pathlib import Path
35
+ from typing import Dict, List, Optional, Any, Callable, Tuple, Set
36
+
37
+ from watchdog.observers import Observer
38
+ from watchdog.events import FileSystemEventHandler, FileSystemEvent
39
+
40
+ from claude_mpm.core.base_service import BaseService
41
+ from claude_mpm.services.shared_prompt_cache import SharedPromptCache
42
+ from claude_mpm.services.agent_registry import AgentRegistry
43
+ from claude_mpm.utils.path_operations import path_ops
44
+ from claude_mpm.utils.config_manager import ConfigurationManager
45
+
46
+
47
+ # ============================================================================
48
+ # Data Models
49
+ # ============================================================================
50
+
51
+ class ModificationType(Enum):
52
+ """Types of agent modifications."""
53
+ CREATE = "create"
54
+ MODIFY = "modify"
55
+ DELETE = "delete"
56
+ MOVE = "move"
57
+ RESTORE = "restore"
58
+
59
+
60
+ class ModificationTier(Enum):
61
+ """Agent hierarchy tiers for modification tracking."""
62
+ PROJECT = "project"
63
+ USER = "user"
64
+ SYSTEM = "system"
65
+
66
+
67
+ @dataclass
68
+ class AgentModification:
69
+ """Agent modification record with comprehensive metadata."""
70
+
71
+ modification_id: str
72
+ agent_name: str
73
+ modification_type: ModificationType
74
+ tier: ModificationTier
75
+ file_path: str
76
+ timestamp: float
77
+ user_id: Optional[str] = None
78
+ modification_details: Dict[str, Any] = field(default_factory=dict)
79
+ file_hash_before: Optional[str] = None
80
+ file_hash_after: Optional[str] = None
81
+ file_size_before: Optional[int] = None
82
+ file_size_after: Optional[int] = None
83
+ backup_path: Optional[str] = None
84
+ validation_status: str = "pending"
85
+ validation_errors: List[str] = field(default_factory=list)
86
+ related_modifications: List[str] = field(default_factory=list)
87
+ metadata: Dict[str, Any] = field(default_factory=dict)
88
+
89
+ @property
90
+ def modification_datetime(self) -> datetime:
91
+ """Get modification timestamp as datetime."""
92
+ return datetime.fromtimestamp(self.timestamp)
93
+
94
+ @property
95
+ def age_seconds(self) -> float:
96
+ """Get age of modification in seconds."""
97
+ return time.time() - self.timestamp
98
+
99
+ def to_dict(self) -> Dict[str, Any]:
100
+ """Convert to dictionary for serialization."""
101
+ data = asdict(self)
102
+ data['modification_type'] = self.modification_type.value
103
+ data['tier'] = self.tier.value
104
+ return data
105
+
106
+ @classmethod
107
+ def from_dict(cls, data: Dict[str, Any]) -> 'AgentModification':
108
+ """Create from dictionary."""
109
+ data['modification_type'] = ModificationType(data['modification_type'])
110
+ data['tier'] = ModificationTier(data['tier'])
111
+ return cls(**data)
112
+
113
+
114
+ @dataclass
115
+ class ModificationHistory:
116
+ """Complete modification history for an agent."""
117
+
118
+ agent_name: str
119
+ modifications: List[AgentModification] = field(default_factory=list)
120
+ current_version: Optional[str] = None
121
+ total_modifications: int = 0
122
+ first_seen: Optional[float] = None
123
+ last_modified: Optional[float] = None
124
+
125
+ def add_modification(self, modification: AgentModification) -> None:
126
+ """Add a modification to history."""
127
+ self.modifications.append(modification)
128
+ self.total_modifications += 1
129
+ self.last_modified = modification.timestamp
130
+
131
+ if self.first_seen is None:
132
+ self.first_seen = modification.timestamp
133
+
134
+ def get_recent_modifications(self, hours: int = 24) -> List[AgentModification]:
135
+ """Get modifications within specified hours."""
136
+ cutoff = time.time() - (hours * 3600)
137
+ return [mod for mod in self.modifications if mod.timestamp >= cutoff]
138
+
139
+ def get_modifications_by_type(self, mod_type: ModificationType) -> List[AgentModification]:
140
+ """Get modifications by type."""
141
+ return [mod for mod in self.modifications if mod.modification_type == mod_type]
142
+
143
+
144
+ # ============================================================================
145
+ # File System Monitoring
146
+ # ============================================================================
147
+
148
+ class AgentFileSystemHandler(FileSystemEventHandler):
149
+ """Handles file system events for agent files."""
150
+
151
+ def __init__(self, tracker: 'AgentModificationTracker'):
152
+ self.tracker = tracker
153
+ self.logger = logging.getLogger(__name__)
154
+
155
+ def on_created(self, event: FileSystemEvent) -> None:
156
+ """Handle file creation events."""
157
+ if not event.is_directory and event.src_path.endswith(('.md', '.json', '.yaml')):
158
+ asyncio.create_task(
159
+ self.tracker._handle_file_modification(event.src_path, ModificationType.CREATE)
160
+ )
161
+
162
+ def on_modified(self, event: FileSystemEvent) -> None:
163
+ """Handle file modification events."""
164
+ if not event.is_directory and event.src_path.endswith(('.md', '.json', '.yaml')):
165
+ asyncio.create_task(
166
+ self.tracker._handle_file_modification(event.src_path, ModificationType.MODIFY)
167
+ )
168
+
169
+ def on_deleted(self, event: FileSystemEvent) -> None:
170
+ """Handle file deletion events."""
171
+ if not event.is_directory and event.src_path.endswith(('.md', '.json', '.yaml')):
172
+ asyncio.create_task(
173
+ self.tracker._handle_file_modification(event.src_path, ModificationType.DELETE)
174
+ )
175
+
176
+ def on_moved(self, event: FileSystemEvent) -> None:
177
+ """Handle file move events."""
178
+ if not event.is_directory and event.src_path.endswith(('.md', '.json', '.yaml')):
179
+ asyncio.create_task(
180
+ self.tracker._handle_file_move(event.src_path, event.dest_path)
181
+ )
182
+
183
+
184
+ # ============================================================================
185
+ # Main Service Class
186
+ # ============================================================================
187
+
188
+ class AgentModificationTracker(BaseService):
189
+ """
190
+ Agent Modification Tracker - Comprehensive modification tracking and persistence system.
191
+
192
+ This consolidated service combines all functionality from the previous multi-file
193
+ implementation into a single, maintainable module.
194
+ """
195
+
196
+ def __init__(self, config: Optional[Dict[str, Any]] = None):
197
+ """Initialize the agent modification tracker."""
198
+ super().__init__("agent_modification_tracker", config)
199
+
200
+ # Configuration
201
+ self.enable_monitoring = self.get_config("enable_monitoring", True)
202
+ self.backup_enabled = self.get_config("backup_enabled", True)
203
+ self.max_history_days = self.get_config("max_history_days", 30)
204
+ self.validation_enabled = self.get_config("validation_enabled", True)
205
+ self.persistence_interval = self.get_config("persistence_interval", 300)
206
+
207
+ # Core components
208
+ self.shared_cache: Optional[SharedPromptCache] = None
209
+ self.agent_registry: Optional[AgentRegistry] = None
210
+
211
+ # Tracking data structures
212
+ self.modification_history: Dict[str, ModificationHistory] = {}
213
+ self.active_modifications: Dict[str, AgentModification] = {}
214
+
215
+ # File monitoring
216
+ self.file_observer: Optional[Observer] = None
217
+ self.watched_paths: Set[Path] = set()
218
+
219
+ # Persistence paths
220
+ self.persistence_root = Path.home() / '.claude-pm' / 'agent_tracking'
221
+ self.backup_root = self.persistence_root / 'backups'
222
+ self.history_root = self.persistence_root / 'history'
223
+
224
+ # Create directories
225
+ self.persistence_root.mkdir(parents=True, exist_ok=True)
226
+ self.backup_root.mkdir(parents=True, exist_ok=True)
227
+ self.history_root.mkdir(parents=True, exist_ok=True)
228
+
229
+ # Background tasks
230
+ self._persistence_task: Optional[asyncio.Task] = None
231
+ self._cleanup_task: Optional[asyncio.Task] = None
232
+
233
+ # Callbacks
234
+ self.modification_callbacks: List[Callable[[AgentModification], None]] = []
235
+
236
+ self.logger.info(
237
+ f"AgentModificationTracker initialized with monitoring="
238
+ f"{'enabled' if self.enable_monitoring else 'disabled'}"
239
+ )
240
+
241
+ async def _initialize(self) -> None:
242
+ """Initialize the modification tracker service."""
243
+ self.logger.info("Initializing AgentModificationTracker service...")
244
+
245
+ # Initialize cache and registry integration
246
+ await self._initialize_integrations()
247
+
248
+ # Load existing modification history
249
+ await self._load_modification_history()
250
+
251
+ # Set up file system monitoring
252
+ if self.enable_monitoring:
253
+ await self._setup_file_monitoring()
254
+
255
+ # Start background tasks
256
+ self._persistence_task = asyncio.create_task(self._persistence_loop())
257
+ self._cleanup_task = asyncio.create_task(self._cleanup_loop())
258
+
259
+ self.logger.info("AgentModificationTracker service initialized successfully")
260
+
261
+ async def _cleanup(self) -> None:
262
+ """Cleanup modification tracker resources."""
263
+ self.logger.info("Cleaning up AgentModificationTracker service...")
264
+
265
+ # Stop file system monitoring
266
+ if self.enable_monitoring and self.file_observer:
267
+ self.file_observer.stop()
268
+ self.file_observer.join()
269
+
270
+ # Cancel background tasks
271
+ if self._persistence_task:
272
+ self._persistence_task.cancel()
273
+ if self._cleanup_task:
274
+ self._cleanup_task.cancel()
275
+
276
+ # Save final state
277
+ await self._save_modification_history()
278
+
279
+ self.logger.info("AgentModificationTracker service cleaned up")
280
+
281
+ async def _health_check(self) -> Dict[str, bool]:
282
+ """Perform modification tracker health checks."""
283
+ checks = {}
284
+
285
+ try:
286
+ # Check persistence directories
287
+ checks["persistence_directories"] = all([
288
+ self.persistence_root.exists(),
289
+ self.backup_root.exists(),
290
+ self.history_root.exists()
291
+ ])
292
+
293
+ # Check file system monitoring
294
+ checks["file_monitoring"] = (
295
+ self.file_observer is not None and
296
+ self.file_observer.is_alive()
297
+ ) if self.enable_monitoring else True
298
+
299
+ # Check integration components
300
+ checks["cache_integration"] = self.shared_cache is not None
301
+ checks["registry_integration"] = self.agent_registry is not None
302
+
303
+ # Check background tasks
304
+ checks["persistence_task"] = (
305
+ self._persistence_task is not None and
306
+ not self._persistence_task.done()
307
+ )
308
+ checks["cleanup_task"] = (
309
+ self._cleanup_task is not None and
310
+ not self._cleanup_task.done()
311
+ )
312
+
313
+ checks["modification_tracking"] = True
314
+
315
+ except Exception as e:
316
+ self.logger.error(f"Modification tracker health check failed: {e}")
317
+ checks["health_check_error"] = False
318
+
319
+ return checks
320
+
321
+ # ========================================================================
322
+ # Core Functionality
323
+ # ========================================================================
324
+
325
+ async def track_modification(self,
326
+ agent_name: str,
327
+ modification_type: ModificationType,
328
+ file_path: str,
329
+ tier: ModificationTier,
330
+ **kwargs) -> AgentModification:
331
+ """Track an agent modification with comprehensive metadata collection."""
332
+ # Generate modification ID
333
+ modification_id = f"{agent_name}_{modification_type.value}_{uuid.uuid4().hex[:8]}"
334
+
335
+ # Collect file metadata
336
+ file_metadata = await self._collect_file_metadata(file_path, modification_type)
337
+
338
+ # Create backup if enabled
339
+ backup_path = None
340
+ if self.backup_enabled and modification_type in [ModificationType.MODIFY, ModificationType.DELETE]:
341
+ backup_path = await self._create_backup(file_path, modification_id)
342
+
343
+ # Create modification record
344
+ modification = AgentModification(
345
+ modification_id=modification_id,
346
+ agent_name=agent_name,
347
+ modification_type=modification_type,
348
+ tier=tier,
349
+ file_path=file_path,
350
+ timestamp=time.time(),
351
+ backup_path=backup_path,
352
+ **file_metadata,
353
+ **kwargs
354
+ )
355
+
356
+ # Validate modification if enabled
357
+ if self.validation_enabled:
358
+ await self._validate_modification(modification)
359
+
360
+ # Store in active modifications
361
+ self.active_modifications[modification_id] = modification
362
+
363
+ # Add to history
364
+ if agent_name not in self.modification_history:
365
+ self.modification_history[agent_name] = ModificationHistory(agent_name=agent_name)
366
+
367
+ self.modification_history[agent_name].add_modification(modification)
368
+
369
+ # Invalidate cache
370
+ if self.shared_cache:
371
+ await self._invalidate_agent_cache(agent_name)
372
+
373
+ # Trigger callbacks
374
+ await self._trigger_modification_callbacks(modification)
375
+
376
+ self.logger.info(
377
+ f"Tracked {modification_type.value} modification for agent '{agent_name}': {modification_id}"
378
+ )
379
+
380
+ return modification
381
+
382
+ # ========================================================================
383
+ # Helper Methods
384
+ # ========================================================================
385
+
386
+ async def _initialize_integrations(self) -> None:
387
+ """Initialize cache and registry integrations."""
388
+ try:
389
+ self.shared_cache = SharedPromptCache.get_instance()
390
+ self.agent_registry = AgentRegistry(cache_service=self.shared_cache)
391
+ self.logger.info("Successfully initialized cache and registry integrations")
392
+ except Exception as e:
393
+ self.logger.warning(f"Failed to initialize integrations: {e}")
394
+
395
+ async def _setup_file_monitoring(self) -> None:
396
+ """Set up file system monitoring for agent files."""
397
+ try:
398
+ self.file_observer = Observer()
399
+ event_handler = AgentFileSystemHandler(self)
400
+
401
+ # Monitor standard agent directories
402
+ agent_dirs = [
403
+ Path("agents"),
404
+ Path("src/claude_mpm/agents"),
405
+ Path.home() / ".claude-pm" / "agents"
406
+ ]
407
+
408
+ for agent_dir in agent_dirs:
409
+ if agent_dir.exists():
410
+ self.file_observer.schedule(event_handler, str(agent_dir), recursive=True)
411
+ self.watched_paths.add(agent_dir)
412
+ self.logger.info(f"Monitoring agent directory: {agent_dir}")
413
+
414
+ self.file_observer.start()
415
+
416
+ except Exception as e:
417
+ self.logger.error(f"Failed to setup file monitoring: {e}")
418
+
419
+ async def _collect_file_metadata(self, file_path: str, modification_type: ModificationType) -> Dict[str, Any]:
420
+ """Collect comprehensive file metadata."""
421
+ metadata = {}
422
+
423
+ try:
424
+ path = Path(file_path)
425
+
426
+ if path.exists() and modification_type != ModificationType.DELETE:
427
+ # File size
428
+ metadata['file_size_after'] = path.stat().st_size
429
+
430
+ # File hash
431
+ with open(path, 'rb') as f:
432
+ metadata['file_hash_after'] = hashlib.sha256(f.read()).hexdigest()
433
+
434
+ # File type
435
+ metadata['file_type'] = path.suffix
436
+
437
+ # Modification time
438
+ metadata['mtime'] = path.stat().st_mtime
439
+
440
+ except Exception as e:
441
+ self.logger.error(f"Error collecting file metadata: {e}")
442
+
443
+ return metadata
444
+
445
+ async def _create_backup(self, file_path: str, modification_id: str) -> Optional[str]:
446
+ """Create backup of agent file."""
447
+ try:
448
+ source = Path(file_path)
449
+ if not source.exists():
450
+ return None
451
+
452
+ # Create backup directory for this modification
453
+ backup_dir = self.backup_root / modification_id
454
+ backup_dir.mkdir(parents=True, exist_ok=True)
455
+
456
+ # Create backup file
457
+ backup_path = backup_dir / source.name
458
+ shutil.copy2(source, backup_path)
459
+
460
+ # Save backup metadata
461
+ metadata = {
462
+ 'original_path': str(file_path),
463
+ 'backup_time': time.time(),
464
+ 'modification_id': modification_id
465
+ }
466
+
467
+ metadata_path = backup_dir / 'metadata.json'
468
+ with open(metadata_path, 'w') as f:
469
+ json.dump(metadata, f, indent=2)
470
+
471
+ return str(backup_path)
472
+
473
+ except Exception as e:
474
+ self.logger.error(f"Failed to create backup: {e}")
475
+ return None
476
+
477
+ async def _validate_modification(self, modification: AgentModification) -> None:
478
+ """Validate agent modification."""
479
+ errors = []
480
+
481
+ try:
482
+ # Check for conflicts
483
+ for mod_id, active_mod in self.active_modifications.items():
484
+ if (active_mod.agent_name == modification.agent_name and
485
+ active_mod.modification_id != modification.modification_id and
486
+ active_mod.age_seconds < 60): # Recent modification
487
+ errors.append(f"Potential conflict with recent modification: {mod_id}")
488
+
489
+ # Validate file path
490
+ if modification.modification_type != ModificationType.DELETE:
491
+ if not Path(modification.file_path).exists():
492
+ errors.append(f"File does not exist: {modification.file_path}")
493
+
494
+ # Update validation status
495
+ modification.validation_status = "failed" if errors else "passed"
496
+ modification.validation_errors = errors
497
+
498
+ except Exception as e:
499
+ self.logger.error(f"Validation error: {e}")
500
+ modification.validation_status = "error"
501
+ modification.validation_errors.append(str(e))
502
+
503
+ async def _invalidate_agent_cache(self, agent_name: str) -> None:
504
+ """Invalidate cache entries for modified agent."""
505
+ if self.shared_cache:
506
+ try:
507
+ # Invalidate all cache entries for this agent
508
+ await self.shared_cache.invalidate_pattern(f"*{agent_name}*")
509
+ self.logger.debug(f"Invalidated cache for agent: {agent_name}")
510
+ except Exception as e:
511
+ self.logger.error(f"Failed to invalidate cache: {e}")
512
+
513
+ async def _handle_file_modification(self, file_path: str, modification_type: ModificationType) -> None:
514
+ """Handle file system modification events."""
515
+ try:
516
+ # Extract agent information from path
517
+ agent_info = self._extract_agent_info_from_path(file_path)
518
+ if not agent_info:
519
+ return
520
+
521
+ agent_name, tier = agent_info
522
+
523
+ # Track the modification
524
+ await self.track_modification(
525
+ agent_name=agent_name,
526
+ modification_type=modification_type,
527
+ file_path=file_path,
528
+ tier=tier,
529
+ source="file_system_monitor"
530
+ )
531
+
532
+ except Exception as e:
533
+ self.logger.error(f"Error handling file modification {file_path}: {e}")
534
+
535
+ async def _handle_file_move(self, src_path: str, dest_path: str) -> None:
536
+ """Handle file move events."""
537
+ try:
538
+ src_info = self._extract_agent_info_from_path(src_path)
539
+
540
+ if src_info:
541
+ agent_name, tier = src_info
542
+ await self.track_modification(
543
+ agent_name=agent_name,
544
+ modification_type=ModificationType.MOVE,
545
+ file_path=dest_path,
546
+ tier=tier,
547
+ source="file_system_monitor",
548
+ move_source=src_path,
549
+ move_destination=dest_path
550
+ )
551
+
552
+ except Exception as e:
553
+ self.logger.error(f"Error handling file move {src_path} -> {dest_path}: {e}")
554
+
555
+ def _extract_agent_info_from_path(self, file_path: str) -> Optional[Tuple[str, ModificationTier]]:
556
+ """Extract agent name and tier from file path."""
557
+ try:
558
+ path = Path(file_path)
559
+
560
+ # Extract agent name from filename
561
+ agent_name = path.stem
562
+ if agent_name.endswith('_agent'):
563
+ agent_name = agent_name[:-6] # Remove _agent suffix
564
+ elif agent_name.endswith('-agent'):
565
+ agent_name = agent_name[:-6] # Remove -agent suffix
566
+
567
+ # Determine tier based on path
568
+ path_str = str(path).lower()
569
+ if 'system' in path_str or '/claude_mpm/agents/' in path_str:
570
+ tier = ModificationTier.SYSTEM
571
+ elif '.claude-pm' in path_str or str(Path.home()) in path_str:
572
+ tier = ModificationTier.USER
573
+ else:
574
+ tier = ModificationTier.PROJECT
575
+
576
+ return agent_name, tier
577
+
578
+ except Exception:
579
+ return None
580
+
581
+ async def _trigger_modification_callbacks(self, modification: AgentModification) -> None:
582
+ """Trigger registered modification callbacks."""
583
+ for callback in self.modification_callbacks:
584
+ try:
585
+ if asyncio.iscoroutinefunction(callback):
586
+ await callback(modification)
587
+ else:
588
+ callback(modification)
589
+ except Exception as e:
590
+ self.logger.error(f"Modification callback failed: {e}")
591
+
592
+ # ========================================================================
593
+ # Persistence Methods
594
+ # ========================================================================
595
+
596
+ async def _load_modification_history(self) -> None:
597
+ """Load modification history from disk."""
598
+ try:
599
+ # Load active modifications
600
+ active_path = self.persistence_root / 'active_modifications.json'
601
+ if active_path.exists():
602
+ with open(active_path, 'r') as f:
603
+ data = json.load(f)
604
+ self.active_modifications = {
605
+ k: AgentModification.from_dict(v) for k, v in data.items()
606
+ }
607
+
608
+ # Load modification history
609
+ for history_file in self.history_root.glob('*.json'):
610
+ with open(history_file, 'r') as f:
611
+ data = json.load(f)
612
+ agent_name = data['agent_name']
613
+ history = ModificationHistory(agent_name=agent_name)
614
+
615
+ # Recreate modifications
616
+ for mod_data in data.get('modifications', []):
617
+ history.add_modification(AgentModification.from_dict(mod_data))
618
+
619
+ history.current_version = data.get('current_version')
620
+ history.first_seen = data.get('first_seen')
621
+ history.last_modified = data.get('last_modified')
622
+
623
+ self.modification_history[agent_name] = history
624
+
625
+ self.logger.info(
626
+ f"Loaded {len(self.active_modifications)} active modifications and "
627
+ f"{len(self.modification_history)} agent histories"
628
+ )
629
+
630
+ except Exception as e:
631
+ self.logger.error(f"Failed to load modification history: {e}")
632
+
633
+ async def _save_modification_history(self) -> None:
634
+ """Save modification history to disk."""
635
+ try:
636
+ # Save active modifications
637
+ active_data = {k: v.to_dict() for k, v in self.active_modifications.items()}
638
+ active_path = self.persistence_root / 'active_modifications.json'
639
+
640
+ with open(active_path, 'w') as f:
641
+ json.dump(active_data, f, indent=2)
642
+
643
+ # Save modification history
644
+ for agent_name, history in self.modification_history.items():
645
+ history_data = {
646
+ 'agent_name': history.agent_name,
647
+ 'modifications': [mod.to_dict() for mod in history.modifications],
648
+ 'current_version': history.current_version,
649
+ 'total_modifications': history.total_modifications,
650
+ 'first_seen': history.first_seen,
651
+ 'last_modified': history.last_modified
652
+ }
653
+
654
+ history_path = self.history_root / f"{agent_name}_history.json"
655
+ with open(history_path, 'w') as f:
656
+ json.dump(history_data, f, indent=2)
657
+
658
+ self.logger.debug("Saved modification history to disk")
659
+
660
+ except Exception as e:
661
+ self.logger.error(f"Failed to save modification history: {e}")
662
+
663
+ async def _persistence_loop(self) -> None:
664
+ """Background task to persist modification history."""
665
+ while not self._stop_event.is_set():
666
+ try:
667
+ await self._save_modification_history()
668
+ await asyncio.sleep(self.persistence_interval)
669
+ except asyncio.CancelledError:
670
+ break
671
+ except Exception as e:
672
+ self.logger.error(f"Persistence loop error: {e}")
673
+ await asyncio.sleep(self.persistence_interval)
674
+
675
+ async def _cleanup_loop(self) -> None:
676
+ """Background task to cleanup old modifications and backups."""
677
+ while not self._stop_event.is_set():
678
+ try:
679
+ await self._cleanup_old_data()
680
+ await asyncio.sleep(3600) # Run every hour
681
+ except asyncio.CancelledError:
682
+ break
683
+ except Exception as e:
684
+ self.logger.error(f"Cleanup loop error: {e}")
685
+ await asyncio.sleep(3600)
686
+
687
+ async def _cleanup_old_data(self) -> None:
688
+ """Clean up old modifications and backups."""
689
+ try:
690
+ cutoff_time = time.time() - (self.max_history_days * 24 * 3600)
691
+
692
+ # Clean up old modifications from active list
693
+ old_active = [
694
+ mod_id for mod_id, mod in self.active_modifications.items()
695
+ if mod.timestamp < cutoff_time
696
+ ]
697
+
698
+ for mod_id in old_active:
699
+ del self.active_modifications[mod_id]
700
+
701
+ # Clean up old backups
702
+ backup_count = 0
703
+ for backup_dir in self.backup_root.iterdir():
704
+ if backup_dir.is_dir():
705
+ metadata_path = backup_dir / 'metadata.json'
706
+ if metadata_path.exists():
707
+ with open(metadata_path, 'r') as f:
708
+ metadata = json.load(f)
709
+ if metadata.get('backup_time', 0) < cutoff_time:
710
+ shutil.rmtree(backup_dir)
711
+ backup_count += 1
712
+
713
+ if old_active or backup_count > 0:
714
+ self.logger.info(
715
+ f"Cleaned up {len(old_active)} old modifications and {backup_count} old backups"
716
+ )
717
+
718
+ except Exception as e:
719
+ self.logger.error(f"Failed to cleanup old data: {e}")
720
+
721
+ # ========================================================================
722
+ # Public API Methods
723
+ # ========================================================================
724
+
725
+ async def get_modification_history(self, agent_name: str) -> Optional[ModificationHistory]:
726
+ """Get modification history for specific agent."""
727
+ return self.modification_history.get(agent_name)
728
+
729
+ async def get_recent_modifications(self, hours: int = 24) -> List[AgentModification]:
730
+ """Get all recent modifications across all agents."""
731
+ cutoff = time.time() - (hours * 3600)
732
+ recent = []
733
+
734
+ for history in self.modification_history.values():
735
+ recent.extend([
736
+ mod for mod in history.modifications
737
+ if mod.timestamp >= cutoff
738
+ ])
739
+
740
+ return sorted(recent, key=lambda x: x.timestamp, reverse=True)
741
+
742
+ async def restore_agent_backup(self, modification_id: str) -> bool:
743
+ """Restore agent from backup."""
744
+ try:
745
+ modification = self.active_modifications.get(modification_id)
746
+ if not modification or not modification.backup_path:
747
+ return False
748
+
749
+ # Restore from backup
750
+ backup_path = Path(modification.backup_path)
751
+ if not backup_path.exists():
752
+ return False
753
+
754
+ original_path = Path(modification.file_path)
755
+ shutil.copy2(backup_path, original_path)
756
+
757
+ # Track restore operation
758
+ await self.track_modification(
759
+ agent_name=modification.agent_name,
760
+ modification_type=ModificationType.RESTORE,
761
+ file_path=modification.file_path,
762
+ tier=modification.tier,
763
+ restored_from=modification_id
764
+ )
765
+
766
+ return True
767
+
768
+ except Exception as e:
769
+ self.logger.error(f"Failed to restore agent backup: {e}")
770
+ return False
771
+
772
+ async def get_modification_stats(self) -> Dict[str, Any]:
773
+ """Get comprehensive modification statistics."""
774
+ stats = {
775
+ 'total_agents_tracked': len(self.modification_history),
776
+ 'total_modifications': sum(h.total_modifications for h in self.modification_history.values()),
777
+ 'active_modifications': len(self.active_modifications),
778
+ 'watched_paths': len(self.watched_paths),
779
+ 'monitoring_enabled': self.enable_monitoring,
780
+ 'backup_enabled': self.backup_enabled,
781
+ 'validation_enabled': self.validation_enabled
782
+ }
783
+
784
+ # Modification type breakdown
785
+ type_counts = {}
786
+ for history in self.modification_history.values():
787
+ for mod in history.modifications:
788
+ type_counts[mod.modification_type.value] = type_counts.get(mod.modification_type.value, 0) + 1
789
+
790
+ stats['modifications_by_type'] = type_counts
791
+
792
+ # Tier breakdown
793
+ tier_counts = {}
794
+ for history in self.modification_history.values():
795
+ for mod in history.modifications:
796
+ tier_counts[mod.tier.value] = tier_counts.get(mod.tier.value, 0) + 1
797
+
798
+ stats['modifications_by_tier'] = tier_counts
799
+
800
+ # Recent activity
801
+ recent_24h = await self.get_recent_modifications(24)
802
+ recent_7d = await self.get_recent_modifications(24 * 7)
803
+
804
+ stats['recent_activity'] = {
805
+ 'last_24_hours': len(recent_24h),
806
+ 'last_7_days': len(recent_7d)
807
+ }
808
+
809
+ # Validation stats
810
+ validation_stats = {
811
+ 'passed': 0,
812
+ 'failed': 0,
813
+ 'pending': 0,
814
+ 'error': 0
815
+ }
816
+
817
+ for mod in self.active_modifications.values():
818
+ validation_stats[mod.validation_status] = validation_stats.get(mod.validation_status, 0) + 1
819
+
820
+ stats['validation_stats'] = validation_stats
821
+
822
+ # Backup stats
823
+ backup_stats = {
824
+ 'total_backups': len(list(self.backup_root.iterdir())),
825
+ 'backup_size_mb': sum(
826
+ f.stat().st_size for f in self.backup_root.rglob('*') if f.is_file()
827
+ ) / (1024 * 1024)
828
+ }
829
+
830
+ stats['backup_stats'] = backup_stats
831
+
832
+ return stats
833
+
834
+ def register_modification_callback(self, callback: Callable[[AgentModification], None]) -> None:
835
+ """Register callback for modification events."""
836
+ self.modification_callbacks.append(callback)
837
+
838
+ def unregister_modification_callback(self, callback: Callable[[AgentModification], None]) -> None:
839
+ """Unregister modification callback."""
840
+ if callback in self.modification_callbacks:
841
+ self.modification_callbacks.remove(callback)