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,279 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Deduplication Manager - Handles INSTRUCTIONS.md/CLAUDE.md file deduplication in parent directory hierarchy
|
|
4
|
-
================================================================================
|
|
5
|
-
|
|
6
|
-
This module manages the deduplication of INSTRUCTIONS.md (and legacy CLAUDE.md) files
|
|
7
|
-
to prevent duplicate context loading in Claude Code.
|
|
8
|
-
|
|
9
|
-
Business Logic:
|
|
10
|
-
- Walk up the directory tree to find all INSTRUCTIONS.md and CLAUDE.md files
|
|
11
|
-
- Identify framework deployment templates vs project files
|
|
12
|
-
- Keep only the rootmost framework template
|
|
13
|
-
- Preserve all project-specific INSTRUCTIONS.md/CLAUDE.md files
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
import os
|
|
17
|
-
from pathlib import Path
|
|
18
|
-
from typing import List, Tuple, Optional, Dict, Any
|
|
19
|
-
from datetime import datetime
|
|
20
|
-
import logging
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class DeduplicationManager:
|
|
24
|
-
"""Manages deduplication of INSTRUCTIONS.md/CLAUDE.md files in parent directories."""
|
|
25
|
-
|
|
26
|
-
def __init__(self, logger: Optional[logging.Logger] = None):
|
|
27
|
-
"""
|
|
28
|
-
Initialize the Deduplication Manager.
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
logger: Logger instance to use
|
|
32
|
-
"""
|
|
33
|
-
self.logger = logger or logging.getLogger(__name__)
|
|
34
|
-
|
|
35
|
-
async def deduplicate_claude_md_files(
|
|
36
|
-
self,
|
|
37
|
-
is_framework_template_func,
|
|
38
|
-
extract_version_func,
|
|
39
|
-
compare_versions_func,
|
|
40
|
-
create_backup_func
|
|
41
|
-
) -> List[Tuple[Path, str]]:
|
|
42
|
-
"""
|
|
43
|
-
Deduplicate INSTRUCTIONS.md/CLAUDE.md files in parent directory hierarchy.
|
|
44
|
-
|
|
45
|
-
BUSINESS LOGIC:
|
|
46
|
-
1. Walk up the directory tree from current working directory to root
|
|
47
|
-
2. Find ALL INSTRUCTIONS.md and CLAUDE.md files in this parent hierarchy
|
|
48
|
-
3. Identify which files are framework deployment templates vs project files
|
|
49
|
-
- Framework templates have title "# Claude PM Framework Configuration - Deployment"
|
|
50
|
-
- Project files (like orchestration test projects) are preserved
|
|
51
|
-
4. Among framework templates, find the one with the newest version
|
|
52
|
-
5. Keep ONLY the rootmost (highest in hierarchy) framework template
|
|
53
|
-
6. Update the rootmost template to the newest version if needed
|
|
54
|
-
7. Backup and remove all other framework templates
|
|
55
|
-
8. Never touch project CLAUDE.md files
|
|
56
|
-
|
|
57
|
-
RATIONALE:
|
|
58
|
-
- Claude Code loads ALL CLAUDE.md files it finds in the directory tree
|
|
59
|
-
- Multiple framework templates cause duplicated context
|
|
60
|
-
- Only one framework template should exist at the rootmost location
|
|
61
|
-
- Project-specific CLAUDE.md files serve different purposes and must be preserved
|
|
62
|
-
|
|
63
|
-
Args:
|
|
64
|
-
is_framework_template_func: Function to check if content is framework template
|
|
65
|
-
extract_version_func: Function to extract version from content
|
|
66
|
-
compare_versions_func: Function to compare two version strings
|
|
67
|
-
create_backup_func: Function to create backup of a file
|
|
68
|
-
|
|
69
|
-
Returns:
|
|
70
|
-
List of tuples (original_path, action_taken) for logging
|
|
71
|
-
"""
|
|
72
|
-
deduplication_actions = []
|
|
73
|
-
|
|
74
|
-
try:
|
|
75
|
-
self.logger.info("🔍 Starting INSTRUCTIONS.md/CLAUDE.md deduplication scan...")
|
|
76
|
-
|
|
77
|
-
# STEP 1: Walk up the directory tree from current directory to root
|
|
78
|
-
current_path = Path.cwd()
|
|
79
|
-
claude_md_files = []
|
|
80
|
-
|
|
81
|
-
# Collect ALL INSTRUCTIONS.md and CLAUDE.md files found while walking up parent directories
|
|
82
|
-
while current_path != current_path.parent: # Stop at root
|
|
83
|
-
# Check for INSTRUCTIONS.md first
|
|
84
|
-
instructions_path = current_path / "INSTRUCTIONS.md"
|
|
85
|
-
if instructions_path.exists() and instructions_path.is_file():
|
|
86
|
-
claude_md_files.append(instructions_path)
|
|
87
|
-
self.logger.debug(f"Found INSTRUCTIONS.md at: {instructions_path}")
|
|
88
|
-
else:
|
|
89
|
-
# Check for legacy CLAUDE.md
|
|
90
|
-
claude_md_path = current_path / "CLAUDE.md"
|
|
91
|
-
if claude_md_path.exists() and claude_md_path.is_file():
|
|
92
|
-
claude_md_files.append(claude_md_path)
|
|
93
|
-
self.logger.debug(f"Found CLAUDE.md at: {claude_md_path}")
|
|
94
|
-
current_path = current_path.parent
|
|
95
|
-
|
|
96
|
-
# Check root directory as well
|
|
97
|
-
root_instructions = current_path / "INSTRUCTIONS.md"
|
|
98
|
-
if root_instructions.exists() and root_instructions.is_file():
|
|
99
|
-
claude_md_files.append(root_instructions)
|
|
100
|
-
self.logger.debug(f"Found INSTRUCTIONS.md at root: {root_instructions}")
|
|
101
|
-
else:
|
|
102
|
-
# Check for legacy CLAUDE.md at root
|
|
103
|
-
root_claude_md = current_path / "CLAUDE.md"
|
|
104
|
-
if root_claude_md.exists() and root_claude_md.is_file():
|
|
105
|
-
claude_md_files.append(root_claude_md)
|
|
106
|
-
self.logger.debug(f"Found CLAUDE.md at root: {root_claude_md}")
|
|
107
|
-
|
|
108
|
-
# If we found 0 or 1 files, there's nothing to deduplicate
|
|
109
|
-
if len(claude_md_files) <= 1:
|
|
110
|
-
self.logger.info("✅ No duplicate INSTRUCTIONS.md/CLAUDE.md files found in parent hierarchy")
|
|
111
|
-
return deduplication_actions
|
|
112
|
-
|
|
113
|
-
# STEP 2: Sort by path depth (rootmost first)
|
|
114
|
-
claude_md_files.sort(key=lambda p: len(p.parts))
|
|
115
|
-
|
|
116
|
-
self.logger.info(f"📊 Found {len(claude_md_files)} INSTRUCTIONS.md/CLAUDE.md files in parent hierarchy")
|
|
117
|
-
|
|
118
|
-
# STEP 3: First pass - analyze all files to categorize and find newest version
|
|
119
|
-
framework_templates = []
|
|
120
|
-
newest_version = None
|
|
121
|
-
newest_content = None
|
|
122
|
-
|
|
123
|
-
for file_path in claude_md_files:
|
|
124
|
-
try:
|
|
125
|
-
content = file_path.read_text()
|
|
126
|
-
# Check if this is a framework deployment template
|
|
127
|
-
is_framework_template = is_framework_template_func(content)
|
|
128
|
-
|
|
129
|
-
if is_framework_template:
|
|
130
|
-
# Extract version from CLAUDE_MD_VERSION metadata
|
|
131
|
-
version = extract_version_func(content)
|
|
132
|
-
framework_templates.append((file_path, content, version))
|
|
133
|
-
|
|
134
|
-
# Track the newest version we've seen
|
|
135
|
-
if version and (newest_version is None or compare_versions_func(version, newest_version) > 0):
|
|
136
|
-
newest_version = version
|
|
137
|
-
newest_content = content
|
|
138
|
-
self.logger.info(f"📋 Found newer version {version} at: {file_path}")
|
|
139
|
-
except Exception as e:
|
|
140
|
-
self.logger.error(f"Failed to analyze {file_path}: {e}")
|
|
141
|
-
|
|
142
|
-
# STEP 4: Second pass - process files based on our analysis
|
|
143
|
-
for idx, file_path in enumerate(claude_md_files):
|
|
144
|
-
try:
|
|
145
|
-
content = file_path.read_text()
|
|
146
|
-
is_framework_template = is_framework_template_func(content)
|
|
147
|
-
|
|
148
|
-
if idx == 0:
|
|
149
|
-
# This is the ROOTMOST file
|
|
150
|
-
if is_framework_template:
|
|
151
|
-
current_version = extract_version_func(content)
|
|
152
|
-
|
|
153
|
-
# Check if we found a newer version elsewhere that we should update to
|
|
154
|
-
if newest_version and current_version and compare_versions_func(newest_version, current_version) > 0:
|
|
155
|
-
# We found a newer version in a subdirectory - update the rootmost file
|
|
156
|
-
|
|
157
|
-
# First backup the current rootmost file
|
|
158
|
-
backup_path = await create_backup_func(file_path)
|
|
159
|
-
if backup_path:
|
|
160
|
-
self.logger.info(f"📁 Backed up current rootmost file before update: {backup_path}")
|
|
161
|
-
|
|
162
|
-
# Update with the newest content we found
|
|
163
|
-
file_path.write_text(newest_content)
|
|
164
|
-
self.logger.info(f"⬆️ Updated rootmost template from {current_version} to {newest_version}")
|
|
165
|
-
deduplication_actions.append((file_path, f"updated from {current_version} to {newest_version}"))
|
|
166
|
-
else:
|
|
167
|
-
# Rootmost file already has the newest version (or versions are equal)
|
|
168
|
-
self.logger.info(f"✅ Keeping primary framework template at: {file_path} (version {current_version})")
|
|
169
|
-
deduplication_actions.append((file_path, "kept as primary"))
|
|
170
|
-
else:
|
|
171
|
-
# Rootmost file is NOT a framework template - this is unusual but we preserve it
|
|
172
|
-
self.logger.info(f"⚠️ File at {file_path} is not a framework template - skipping")
|
|
173
|
-
deduplication_actions.append((file_path, "skipped - not framework template"))
|
|
174
|
-
else:
|
|
175
|
-
# This is NOT the rootmost file
|
|
176
|
-
if is_framework_template:
|
|
177
|
-
# This is a REDUNDANT framework template that must be removed
|
|
178
|
-
|
|
179
|
-
# Generate backup filename with timestamp
|
|
180
|
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
181
|
-
backup_name = f"CLAUDE.md.backup_{timestamp}"
|
|
182
|
-
backup_path = file_path.parent / backup_name
|
|
183
|
-
|
|
184
|
-
# Handle duplicate timestamps (rare but possible)
|
|
185
|
-
counter = 1
|
|
186
|
-
while backup_path.exists():
|
|
187
|
-
backup_name = f"CLAUDE.md.backup_{timestamp}_{counter:02d}"
|
|
188
|
-
backup_path = file_path.parent / backup_name
|
|
189
|
-
counter += 1
|
|
190
|
-
|
|
191
|
-
# Rename the duplicate to backup
|
|
192
|
-
file_path.rename(backup_path)
|
|
193
|
-
self.logger.warning(f"📦 Backed up duplicate framework template: {file_path} → {backup_path}")
|
|
194
|
-
deduplication_actions.append((file_path, f"backed up to {backup_path.name}"))
|
|
195
|
-
else:
|
|
196
|
-
# This is a PROJECT-SPECIFIC CLAUDE.md file - NEVER REMOVE THESE
|
|
197
|
-
self.logger.info(f"🛡️ Preserving non-framework file at: {file_path}")
|
|
198
|
-
deduplication_actions.append((file_path, "preserved - not framework template"))
|
|
199
|
-
|
|
200
|
-
except Exception as file_error:
|
|
201
|
-
self.logger.error(f"Failed to process file {file_path}: {file_error}")
|
|
202
|
-
deduplication_actions.append((file_path, f"error: {str(file_error)}"))
|
|
203
|
-
|
|
204
|
-
# Log summary
|
|
205
|
-
backed_up_count = sum(1 for _, action in deduplication_actions if "backed up" in action)
|
|
206
|
-
if backed_up_count > 0:
|
|
207
|
-
self.logger.info(f"✅ Deduplication complete: {backed_up_count} duplicate framework templates backed up")
|
|
208
|
-
else:
|
|
209
|
-
self.logger.info("✅ Deduplication complete: No framework template duplicates needed backing up")
|
|
210
|
-
|
|
211
|
-
except Exception as e:
|
|
212
|
-
self.logger.error(f"Failed during CLAUDE.md deduplication: {e}")
|
|
213
|
-
|
|
214
|
-
return deduplication_actions
|
|
215
|
-
|
|
216
|
-
async def deduplicate_parent_claude_md(
|
|
217
|
-
self,
|
|
218
|
-
is_framework_template_func,
|
|
219
|
-
extract_version_func,
|
|
220
|
-
compare_versions_func,
|
|
221
|
-
create_backup_func
|
|
222
|
-
) -> Dict[str, Any]:
|
|
223
|
-
"""
|
|
224
|
-
Public method to manually trigger CLAUDE.md deduplication in parent hierarchy.
|
|
225
|
-
|
|
226
|
-
Args:
|
|
227
|
-
is_framework_template_func: Function to check if content is framework template
|
|
228
|
-
extract_version_func: Function to extract version from content
|
|
229
|
-
compare_versions_func: Function to compare two version strings
|
|
230
|
-
create_backup_func: Function to create backup of a file
|
|
231
|
-
|
|
232
|
-
Returns:
|
|
233
|
-
Dictionary with deduplication results
|
|
234
|
-
"""
|
|
235
|
-
try:
|
|
236
|
-
self.logger.info("🔧 Manual CLAUDE.md deduplication requested")
|
|
237
|
-
|
|
238
|
-
# Run deduplication
|
|
239
|
-
actions = await self.deduplicate_claude_md_files(
|
|
240
|
-
is_framework_template_func,
|
|
241
|
-
extract_version_func,
|
|
242
|
-
compare_versions_func,
|
|
243
|
-
create_backup_func
|
|
244
|
-
)
|
|
245
|
-
|
|
246
|
-
# Build result summary
|
|
247
|
-
result = {
|
|
248
|
-
"success": True,
|
|
249
|
-
"timestamp": datetime.now().isoformat(),
|
|
250
|
-
"actions_taken": len(actions),
|
|
251
|
-
"details": []
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
for file_path, action in actions:
|
|
255
|
-
result["details"].append({
|
|
256
|
-
"file": str(file_path),
|
|
257
|
-
"action": action
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
# Count different action types
|
|
261
|
-
action_summary = {
|
|
262
|
-
"kept_primary": sum(1 for _, a in actions if "kept as primary" in a),
|
|
263
|
-
"backed_up": sum(1 for _, a in actions if "backed up" in a),
|
|
264
|
-
"preserved": sum(1 for _, a in actions if "preserved" in a),
|
|
265
|
-
"skipped": sum(1 for _, a in actions if "skipped" in a),
|
|
266
|
-
"errors": sum(1 for _, a in actions if "error:" in a)
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
result["summary"] = action_summary
|
|
270
|
-
|
|
271
|
-
return result
|
|
272
|
-
|
|
273
|
-
except Exception as e:
|
|
274
|
-
self.logger.error(f"Failed to run manual deduplication: {e}")
|
|
275
|
-
return {
|
|
276
|
-
"success": False,
|
|
277
|
-
"error": str(e),
|
|
278
|
-
"timestamp": datetime.now().isoformat()
|
|
279
|
-
}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
"""Framework protection module for critical framework files.
|
|
2
|
-
|
|
3
|
-
This module provides the FrameworkProtector class which ensures critical
|
|
4
|
-
framework files like framework/INSTRUCTIONS.md (legacy: framework/CLAUDE.md)
|
|
5
|
-
are protected from accidental deletion and maintain proper permissions.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import os
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
from typing import Optional
|
|
11
|
-
import logging
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class FrameworkProtector:
|
|
15
|
-
"""Protects critical framework files from deletion and corruption."""
|
|
16
|
-
|
|
17
|
-
def __init__(self, framework_path: Path, logger: Optional[logging.Logger] = None):
|
|
18
|
-
"""Initialize the FrameworkProtector.
|
|
19
|
-
|
|
20
|
-
Args:
|
|
21
|
-
framework_path: Path to the framework directory
|
|
22
|
-
logger: Optional logger instance for logging protection activities
|
|
23
|
-
"""
|
|
24
|
-
self.framework_path = framework_path
|
|
25
|
-
self.logger = logger or logging.getLogger(__name__)
|
|
26
|
-
|
|
27
|
-
def protect_framework_template(self, framework_template_path: Path) -> None:
|
|
28
|
-
"""Ensure framework template is protected from deletion.
|
|
29
|
-
|
|
30
|
-
This method verifies that the framework template exists, is the correct
|
|
31
|
-
file, and has proper read permissions. This is critical to prevent
|
|
32
|
-
accidental deletion of the master framework template.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
framework_template_path: Path to the framework template file
|
|
36
|
-
"""
|
|
37
|
-
try:
|
|
38
|
-
# Verify the file exists
|
|
39
|
-
if not framework_template_path.exists():
|
|
40
|
-
self.logger.warning(
|
|
41
|
-
f"Framework template not found at {framework_template_path}"
|
|
42
|
-
)
|
|
43
|
-
return
|
|
44
|
-
|
|
45
|
-
# Verify this is actually the framework template
|
|
46
|
-
# Check that the filename is INSTRUCTIONS.md or CLAUDE.md (legacy) and path contains "framework"
|
|
47
|
-
if (framework_template_path.name not in ["INSTRUCTIONS.md", "CLAUDE.md"] or
|
|
48
|
-
"framework" not in str(framework_template_path)):
|
|
49
|
-
self.logger.debug(
|
|
50
|
-
f"Path {framework_template_path} does not appear to be "
|
|
51
|
-
"the framework template"
|
|
52
|
-
)
|
|
53
|
-
return
|
|
54
|
-
|
|
55
|
-
# Ensure file has proper permissions (readable)
|
|
56
|
-
try:
|
|
57
|
-
# Get current permissions
|
|
58
|
-
current_mode = framework_template_path.stat().st_mode
|
|
59
|
-
|
|
60
|
-
# Ensure owner can read (add read permission if needed)
|
|
61
|
-
if not (current_mode & 0o400):
|
|
62
|
-
new_mode = current_mode | 0o400 # Add owner read
|
|
63
|
-
os.chmod(framework_template_path, new_mode)
|
|
64
|
-
self.logger.info(
|
|
65
|
-
f"Added read permission to framework template at "
|
|
66
|
-
f"{framework_template_path}"
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
# Log successful protection
|
|
70
|
-
self.logger.debug(
|
|
71
|
-
f"Framework template protected at {framework_template_path}"
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
except Exception as e:
|
|
75
|
-
self.logger.error(
|
|
76
|
-
f"Failed to set permissions on framework template: {e}"
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
except Exception as e:
|
|
80
|
-
self.logger.error(
|
|
81
|
-
f"Error protecting framework template at "
|
|
82
|
-
f"{framework_template_path}: {e}"
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
def validate_framework_template_integrity(self, content: str) -> bool:
|
|
86
|
-
"""Validate that framework template has proper structure and metadata.
|
|
87
|
-
|
|
88
|
-
This method checks that the template contains essential metadata
|
|
89
|
-
and structure required for proper framework operation.
|
|
90
|
-
|
|
91
|
-
Args:
|
|
92
|
-
content: The content of the framework template to validate
|
|
93
|
-
|
|
94
|
-
Returns:
|
|
95
|
-
bool: True if template is valid, False otherwise
|
|
96
|
-
"""
|
|
97
|
-
try:
|
|
98
|
-
# Check for essential metadata markers
|
|
99
|
-
required_markers = [
|
|
100
|
-
"CLAUDE_MD_VERSION:",
|
|
101
|
-
"FRAMEWORK_VERSION:",
|
|
102
|
-
"AI ASSISTANT ROLE DESIGNATION",
|
|
103
|
-
"AGENTS",
|
|
104
|
-
"TODO AND TASK TOOLS"
|
|
105
|
-
]
|
|
106
|
-
|
|
107
|
-
# Check each required marker exists in content
|
|
108
|
-
for marker in required_markers:
|
|
109
|
-
if marker not in content:
|
|
110
|
-
self.logger.warning(
|
|
111
|
-
f"Framework template missing required marker: {marker}"
|
|
112
|
-
)
|
|
113
|
-
return False
|
|
114
|
-
|
|
115
|
-
# Check for handlebars variables that should be present
|
|
116
|
-
required_variables = [
|
|
117
|
-
"{{DEPLOYMENT_ID}}",
|
|
118
|
-
"{{PLATFORM_NOTES}}"
|
|
119
|
-
]
|
|
120
|
-
|
|
121
|
-
for variable in required_variables:
|
|
122
|
-
if variable not in content:
|
|
123
|
-
self.logger.warning(
|
|
124
|
-
f"Framework template missing required variable: {variable}"
|
|
125
|
-
)
|
|
126
|
-
return False
|
|
127
|
-
|
|
128
|
-
# Validate minimum content length (templates should be substantial)
|
|
129
|
-
min_content_length = 1000 # Reasonable minimum for a valid template
|
|
130
|
-
if len(content) < min_content_length:
|
|
131
|
-
self.logger.warning(
|
|
132
|
-
f"Framework template content too short: {len(content)} chars "
|
|
133
|
-
f"(minimum: {min_content_length})"
|
|
134
|
-
)
|
|
135
|
-
return False
|
|
136
|
-
|
|
137
|
-
# If all checks pass, template is valid
|
|
138
|
-
self.logger.debug("Framework template integrity validated successfully")
|
|
139
|
-
return True
|
|
140
|
-
|
|
141
|
-
except Exception as e:
|
|
142
|
-
self.logger.error(f"Error validating framework template integrity: {e}")
|
|
143
|
-
return False
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Parent Directory Operations - Handles directory context detection and registration
|
|
4
|
-
================================================================================
|
|
5
|
-
|
|
6
|
-
This module manages parent directory context detection, auto-registration,
|
|
7
|
-
and template variable generation.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import os
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import List, Dict, Any, Optional
|
|
13
|
-
from datetime import datetime
|
|
14
|
-
from enum import Enum
|
|
15
|
-
import logging
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class ParentDirectoryContext(Enum):
|
|
19
|
-
"""Context types for parent directory management."""
|
|
20
|
-
DEPLOYMENT_ROOT = "deployment_root"
|
|
21
|
-
PROJECT_COLLECTION = "project_collection"
|
|
22
|
-
WORKSPACE_ROOT = "workspace_root"
|
|
23
|
-
USER_HOME = "user_home"
|
|
24
|
-
CUSTOM = "custom"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class ParentDirectoryOperations:
|
|
28
|
-
"""Manages parent directory detection and operations."""
|
|
29
|
-
|
|
30
|
-
def __init__(self, logger: Optional[logging.Logger] = None):
|
|
31
|
-
"""
|
|
32
|
-
Initialize the Parent Directory Operations.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
logger: Logger instance to use
|
|
36
|
-
"""
|
|
37
|
-
self.logger = logger or logging.getLogger(__name__)
|
|
38
|
-
|
|
39
|
-
async def detect_parent_directory_context(
|
|
40
|
-
self, target_directory: Path
|
|
41
|
-
) -> ParentDirectoryContext:
|
|
42
|
-
"""
|
|
43
|
-
Detect the context of a parent directory.
|
|
44
|
-
|
|
45
|
-
Args:
|
|
46
|
-
target_directory: Directory to analyze
|
|
47
|
-
|
|
48
|
-
Returns:
|
|
49
|
-
ParentDirectoryContext enum value
|
|
50
|
-
"""
|
|
51
|
-
try:
|
|
52
|
-
# Check if it's the user home directory
|
|
53
|
-
if target_directory == Path.home():
|
|
54
|
-
return ParentDirectoryContext.USER_HOME
|
|
55
|
-
|
|
56
|
-
# Check if it contains a deployment (has claude-multiagent-pm subdirectory)
|
|
57
|
-
if (target_directory / "claude-multiagent-pm").exists():
|
|
58
|
-
return ParentDirectoryContext.DEPLOYMENT_ROOT
|
|
59
|
-
|
|
60
|
-
# Check if it contains multiple projects
|
|
61
|
-
subdirs = [d for d in target_directory.iterdir() if d.is_dir()]
|
|
62
|
-
project_indicators = [".git", "package.json", "pyproject.toml", "Cargo.toml"]
|
|
63
|
-
|
|
64
|
-
project_count = 0
|
|
65
|
-
for subdir in subdirs:
|
|
66
|
-
if any((subdir / indicator).exists() for indicator in project_indicators):
|
|
67
|
-
project_count += 1
|
|
68
|
-
|
|
69
|
-
if project_count > 1:
|
|
70
|
-
return ParentDirectoryContext.PROJECT_COLLECTION
|
|
71
|
-
|
|
72
|
-
# Check if it's a workspace root
|
|
73
|
-
workspace_indicators = [".vscode", ".idea", "workspace.json"]
|
|
74
|
-
if any((target_directory / indicator).exists() for indicator in workspace_indicators):
|
|
75
|
-
return ParentDirectoryContext.WORKSPACE_ROOT
|
|
76
|
-
|
|
77
|
-
# Default to custom
|
|
78
|
-
return ParentDirectoryContext.CUSTOM
|
|
79
|
-
|
|
80
|
-
except Exception as e:
|
|
81
|
-
self.logger.error(
|
|
82
|
-
f"Failed to detect parent directory context for {target_directory}: {e}"
|
|
83
|
-
)
|
|
84
|
-
return ParentDirectoryContext.CUSTOM
|
|
85
|
-
|
|
86
|
-
async def auto_register_parent_directories(
|
|
87
|
-
self,
|
|
88
|
-
search_paths: List[Path],
|
|
89
|
-
template_id: str,
|
|
90
|
-
register_func,
|
|
91
|
-
get_default_variables_func
|
|
92
|
-
) -> List[Path]:
|
|
93
|
-
"""
|
|
94
|
-
Automatically register parent directories that should be managed.
|
|
95
|
-
|
|
96
|
-
Args:
|
|
97
|
-
search_paths: Paths to search for parent directories
|
|
98
|
-
template_id: Template to use for auto-registration
|
|
99
|
-
register_func: Function to register directory
|
|
100
|
-
get_default_variables_func: Function to get default template variables
|
|
101
|
-
|
|
102
|
-
Returns:
|
|
103
|
-
List of registered directories
|
|
104
|
-
"""
|
|
105
|
-
try:
|
|
106
|
-
registered_directories = []
|
|
107
|
-
|
|
108
|
-
for search_path in search_paths:
|
|
109
|
-
if not search_path.exists() or not search_path.is_dir():
|
|
110
|
-
continue
|
|
111
|
-
|
|
112
|
-
# Check if this directory should be managed
|
|
113
|
-
context = await self.detect_parent_directory_context(search_path)
|
|
114
|
-
|
|
115
|
-
# Skip user home directory unless explicitly configured
|
|
116
|
-
if context == ParentDirectoryContext.USER_HOME:
|
|
117
|
-
continue
|
|
118
|
-
|
|
119
|
-
# Auto-register if it looks like a deployment root or project collection
|
|
120
|
-
if context in [
|
|
121
|
-
ParentDirectoryContext.DEPLOYMENT_ROOT,
|
|
122
|
-
ParentDirectoryContext.PROJECT_COLLECTION,
|
|
123
|
-
]:
|
|
124
|
-
success = await register_func(
|
|
125
|
-
search_path,
|
|
126
|
-
context,
|
|
127
|
-
template_id,
|
|
128
|
-
get_default_variables_func(search_path, context),
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
if success:
|
|
132
|
-
registered_directories.append(search_path)
|
|
133
|
-
self.logger.info(f"Auto-registered parent directory: {search_path}")
|
|
134
|
-
|
|
135
|
-
return registered_directories
|
|
136
|
-
|
|
137
|
-
except Exception as e:
|
|
138
|
-
self.logger.error(f"Failed to auto-register parent directories: {e}")
|
|
139
|
-
return []
|
|
140
|
-
|
|
141
|
-
def get_default_template_variables(
|
|
142
|
-
self,
|
|
143
|
-
target_directory: Path,
|
|
144
|
-
context: ParentDirectoryContext,
|
|
145
|
-
deployment_context: Optional[Dict[str, Any]] = None
|
|
146
|
-
) -> Dict[str, Any]:
|
|
147
|
-
"""Get default template variables for a directory."""
|
|
148
|
-
variables = {
|
|
149
|
-
"DIRECTORY_PATH": str(target_directory),
|
|
150
|
-
"DIRECTORY_NAME": target_directory.name,
|
|
151
|
-
"CONTEXT": context.value,
|
|
152
|
-
"TIMESTAMP": datetime.now().isoformat(),
|
|
153
|
-
"PLATFORM": os.name,
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
# Add deployment-specific variables if available
|
|
157
|
-
if deployment_context:
|
|
158
|
-
variables.update(
|
|
159
|
-
{
|
|
160
|
-
"DEPLOYMENT_TYPE": deployment_context.get("strategy", "unknown"),
|
|
161
|
-
"DEPLOYMENT_PLATFORM": deployment_context.get("config", {}).get(
|
|
162
|
-
"platform", "unknown"
|
|
163
|
-
),
|
|
164
|
-
}
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
return variables
|
|
168
|
-
|
|
169
|
-
def get_platform_notes(self) -> str:
|
|
170
|
-
"""
|
|
171
|
-
Get platform-specific notes for the framework template.
|
|
172
|
-
|
|
173
|
-
Returns:
|
|
174
|
-
Platform-specific notes string
|
|
175
|
-
"""
|
|
176
|
-
import platform
|
|
177
|
-
system = platform.system().lower()
|
|
178
|
-
|
|
179
|
-
if system == 'windows':
|
|
180
|
-
return "Windows users may need to use 'python' instead of 'python3' depending on installation."
|
|
181
|
-
elif system == 'darwin':
|
|
182
|
-
return "macOS users should ensure python3 is installed via Homebrew or official Python installer."
|
|
183
|
-
elif system == 'linux':
|
|
184
|
-
return "Linux users may need to install python3 via their package manager if not present."
|
|
185
|
-
else:
|
|
186
|
-
return f"Platform-specific configuration may be required for {system}."
|