claude-mpm 4.3.22__py3-none-any.whl → 4.4.3__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/WORKFLOW.md +2 -14
- claude_mpm/cli/commands/configure.py +2 -29
- claude_mpm/cli/commands/doctor.py +2 -2
- claude_mpm/cli/commands/mpm_init.py +3 -3
- claude_mpm/cli/parsers/configure_parser.py +4 -15
- claude_mpm/core/framework/__init__.py +38 -0
- claude_mpm/core/framework/formatters/__init__.py +11 -0
- claude_mpm/core/framework/formatters/capability_generator.py +356 -0
- claude_mpm/core/framework/formatters/content_formatter.py +283 -0
- claude_mpm/core/framework/formatters/context_generator.py +180 -0
- claude_mpm/core/framework/loaders/__init__.py +13 -0
- claude_mpm/core/framework/loaders/agent_loader.py +202 -0
- claude_mpm/core/framework/loaders/file_loader.py +213 -0
- claude_mpm/core/framework/loaders/instruction_loader.py +151 -0
- claude_mpm/core/framework/loaders/packaged_loader.py +208 -0
- claude_mpm/core/framework/processors/__init__.py +11 -0
- claude_mpm/core/framework/processors/memory_processor.py +222 -0
- claude_mpm/core/framework/processors/metadata_processor.py +146 -0
- claude_mpm/core/framework/processors/template_processor.py +238 -0
- claude_mpm/core/framework_loader.py +277 -1798
- claude_mpm/hooks/__init__.py +9 -1
- claude_mpm/hooks/kuzu_memory_hook.py +352 -0
- claude_mpm/hooks/memory_integration_hook.py +1 -1
- claude_mpm/services/agents/memory/content_manager.py +5 -2
- claude_mpm/services/agents/memory/memory_file_service.py +1 -0
- claude_mpm/services/agents/memory/memory_limits_service.py +1 -0
- claude_mpm/services/core/path_resolver.py +1 -0
- claude_mpm/services/diagnostics/diagnostic_runner.py +1 -0
- claude_mpm/services/mcp_config_manager.py +67 -4
- claude_mpm/services/mcp_gateway/core/process_pool.py +281 -0
- claude_mpm/services/mcp_gateway/core/startup_verification.py +2 -2
- claude_mpm/services/mcp_gateway/main.py +3 -13
- claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -10
- claude_mpm/services/mcp_gateway/tools/__init__.py +13 -2
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +36 -6
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +542 -0
- claude_mpm/services/shared/__init__.py +2 -1
- claude_mpm/services/shared/service_factory.py +8 -5
- claude_mpm/services/unified/__init__.py +65 -0
- claude_mpm/services/unified/analyzer_strategies/__init__.py +44 -0
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +473 -0
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +643 -0
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +804 -0
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +661 -0
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +696 -0
- claude_mpm/services/unified/config_strategies/__init__.py +190 -0
- claude_mpm/services/unified/config_strategies/config_schema.py +689 -0
- claude_mpm/services/unified/config_strategies/context_strategy.py +748 -0
- claude_mpm/services/unified/config_strategies/error_handling_strategy.py +999 -0
- claude_mpm/services/unified/config_strategies/file_loader_strategy.py +871 -0
- claude_mpm/services/unified/config_strategies/unified_config_service.py +802 -0
- claude_mpm/services/unified/config_strategies/validation_strategy.py +1105 -0
- claude_mpm/services/unified/deployment_strategies/__init__.py +97 -0
- claude_mpm/services/unified/deployment_strategies/base.py +557 -0
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +486 -0
- claude_mpm/services/unified/deployment_strategies/local.py +594 -0
- claude_mpm/services/unified/deployment_strategies/utils.py +672 -0
- claude_mpm/services/unified/deployment_strategies/vercel.py +471 -0
- claude_mpm/services/unified/interfaces.py +499 -0
- claude_mpm/services/unified/migration.py +532 -0
- claude_mpm/services/unified/strategies.py +551 -0
- claude_mpm/services/unified/unified_analyzer.py +534 -0
- claude_mpm/services/unified/unified_config.py +688 -0
- claude_mpm/services/unified/unified_deployment.py +470 -0
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/METADATA +15 -15
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/RECORD +71 -32
- claude_mpm/cli/commands/configure_tui.py +0 -1927
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +0 -645
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +0 -602
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/WHEEL +0 -0
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,213 @@
|
|
1
|
+
"""File loading utilities for the framework."""
|
2
|
+
|
3
|
+
import logging
|
4
|
+
import re
|
5
|
+
from pathlib import Path
|
6
|
+
from typing import Optional
|
7
|
+
|
8
|
+
from claude_mpm.core.logging_utils import get_logger
|
9
|
+
|
10
|
+
|
11
|
+
class FileLoader:
|
12
|
+
"""Handles file I/O operations for the framework."""
|
13
|
+
|
14
|
+
def __init__(self):
|
15
|
+
"""Initialize the file loader."""
|
16
|
+
self.logger = get_logger("file_loader")
|
17
|
+
self.framework_version: Optional[str] = None
|
18
|
+
self.framework_last_modified: Optional[str] = None
|
19
|
+
|
20
|
+
def try_load_file(self, file_path: Path, file_type: str) -> Optional[str]:
|
21
|
+
"""
|
22
|
+
Try to load a file with error handling.
|
23
|
+
|
24
|
+
Args:
|
25
|
+
file_path: Path to the file to load
|
26
|
+
file_type: Description of file type for logging
|
27
|
+
|
28
|
+
Returns:
|
29
|
+
File content if successful, None otherwise
|
30
|
+
"""
|
31
|
+
try:
|
32
|
+
content = file_path.read_text()
|
33
|
+
if self.logger.isEnabledFor(logging.INFO):
|
34
|
+
self.logger.info(f"Loaded {file_type} from: {file_path}")
|
35
|
+
|
36
|
+
# Extract metadata if present
|
37
|
+
self._extract_metadata(content, file_path)
|
38
|
+
return content
|
39
|
+
except Exception as e:
|
40
|
+
if self.logger.isEnabledFor(logging.ERROR):
|
41
|
+
self.logger.error(f"Failed to load {file_type}: {e}")
|
42
|
+
return None
|
43
|
+
|
44
|
+
def _extract_metadata(self, content: str, file_path: Path) -> None:
|
45
|
+
"""Extract metadata from file content.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
content: File content to extract metadata from
|
49
|
+
file_path: Path to the file (for context)
|
50
|
+
"""
|
51
|
+
# Extract version
|
52
|
+
version_match = re.search(r"<!-- FRAMEWORK_VERSION: (\d+) -->", content)
|
53
|
+
if version_match:
|
54
|
+
version = version_match.group(1) # Keep as string to preserve leading zeros
|
55
|
+
self.logger.info(f"Framework version: {version}")
|
56
|
+
# Store framework version if this is the main INSTRUCTIONS.md
|
57
|
+
if "INSTRUCTIONS.md" in str(file_path):
|
58
|
+
self.framework_version = version
|
59
|
+
|
60
|
+
# Extract modification timestamp
|
61
|
+
timestamp_match = re.search(r"<!-- LAST_MODIFIED: ([^>]+) -->", content)
|
62
|
+
if timestamp_match:
|
63
|
+
timestamp = timestamp_match.group(1).strip()
|
64
|
+
self.logger.info(f"Last modified: {timestamp}")
|
65
|
+
# Store timestamp if this is the main INSTRUCTIONS.md
|
66
|
+
if "INSTRUCTIONS.md" in str(file_path):
|
67
|
+
self.framework_last_modified = timestamp
|
68
|
+
|
69
|
+
def load_instructions_file(self, current_dir: Path) -> tuple[Optional[str], Optional[str]]:
|
70
|
+
"""
|
71
|
+
Load custom INSTRUCTIONS.md from .claude-mpm directories.
|
72
|
+
|
73
|
+
Precedence (highest to lowest):
|
74
|
+
1. Project-specific: ./.claude-mpm/INSTRUCTIONS.md
|
75
|
+
2. User-specific: ~/.claude-mpm/INSTRUCTIONS.md
|
76
|
+
|
77
|
+
Args:
|
78
|
+
current_dir: Current working directory
|
79
|
+
|
80
|
+
Returns:
|
81
|
+
Tuple of (content, level) where level is 'project', 'user', or None
|
82
|
+
"""
|
83
|
+
# Check for project-specific INSTRUCTIONS.md first
|
84
|
+
project_instructions_path = current_dir / ".claude-mpm" / "INSTRUCTIONS.md"
|
85
|
+
if project_instructions_path.exists():
|
86
|
+
loaded_content = self.try_load_file(
|
87
|
+
project_instructions_path, "project-specific INSTRUCTIONS.md"
|
88
|
+
)
|
89
|
+
if loaded_content:
|
90
|
+
self.logger.info(
|
91
|
+
"Using project-specific PM instructions from .claude-mpm/INSTRUCTIONS.md"
|
92
|
+
)
|
93
|
+
return loaded_content, "project"
|
94
|
+
|
95
|
+
# Check for user-specific INSTRUCTIONS.md
|
96
|
+
user_instructions_path = Path.home() / ".claude-mpm" / "INSTRUCTIONS.md"
|
97
|
+
if user_instructions_path.exists():
|
98
|
+
loaded_content = self.try_load_file(
|
99
|
+
user_instructions_path, "user-specific INSTRUCTIONS.md"
|
100
|
+
)
|
101
|
+
if loaded_content:
|
102
|
+
self.logger.info(
|
103
|
+
"Using user-specific PM instructions from ~/.claude-mpm/INSTRUCTIONS.md"
|
104
|
+
)
|
105
|
+
return loaded_content, "user"
|
106
|
+
|
107
|
+
return None, None
|
108
|
+
|
109
|
+
def load_workflow_file(self, current_dir: Path, framework_path: Path) -> tuple[Optional[str], Optional[str]]:
|
110
|
+
"""
|
111
|
+
Load WORKFLOW.md from various locations.
|
112
|
+
|
113
|
+
Precedence (highest to lowest):
|
114
|
+
1. Project-specific: ./.claude-mpm/WORKFLOW.md
|
115
|
+
2. User-specific: ~/.claude-mpm/WORKFLOW.md
|
116
|
+
3. System default: framework/agents/WORKFLOW.md
|
117
|
+
|
118
|
+
Args:
|
119
|
+
current_dir: Current working directory
|
120
|
+
framework_path: Path to framework installation
|
121
|
+
|
122
|
+
Returns:
|
123
|
+
Tuple of (content, level) where level is 'project', 'user', 'system', or None
|
124
|
+
"""
|
125
|
+
# Check for project-specific WORKFLOW.md first (highest priority)
|
126
|
+
project_workflow_path = current_dir / ".claude-mpm" / "WORKFLOW.md"
|
127
|
+
if project_workflow_path.exists():
|
128
|
+
loaded_content = self.try_load_file(
|
129
|
+
project_workflow_path, "project-specific WORKFLOW.md"
|
130
|
+
)
|
131
|
+
if loaded_content:
|
132
|
+
self.logger.info(
|
133
|
+
"Using project-specific workflow instructions from .claude-mpm/WORKFLOW.md"
|
134
|
+
)
|
135
|
+
return loaded_content, "project"
|
136
|
+
|
137
|
+
# Check for user-specific WORKFLOW.md (medium priority)
|
138
|
+
user_workflow_path = Path.home() / ".claude-mpm" / "WORKFLOW.md"
|
139
|
+
if user_workflow_path.exists():
|
140
|
+
loaded_content = self.try_load_file(
|
141
|
+
user_workflow_path, "user-specific WORKFLOW.md"
|
142
|
+
)
|
143
|
+
if loaded_content:
|
144
|
+
self.logger.info(
|
145
|
+
"Using user-specific workflow instructions from ~/.claude-mpm/WORKFLOW.md"
|
146
|
+
)
|
147
|
+
return loaded_content, "user"
|
148
|
+
|
149
|
+
# Fall back to system workflow (lowest priority)
|
150
|
+
if framework_path and framework_path != Path("__PACKAGED__"):
|
151
|
+
system_workflow_path = framework_path / "src" / "claude_mpm" / "agents" / "WORKFLOW.md"
|
152
|
+
if system_workflow_path.exists():
|
153
|
+
loaded_content = self.try_load_file(
|
154
|
+
system_workflow_path, "system WORKFLOW.md"
|
155
|
+
)
|
156
|
+
if loaded_content:
|
157
|
+
self.logger.info("Using system workflow instructions")
|
158
|
+
return loaded_content, "system"
|
159
|
+
|
160
|
+
return None, None
|
161
|
+
|
162
|
+
def load_memory_file(self, current_dir: Path, framework_path: Path) -> tuple[Optional[str], Optional[str]]:
|
163
|
+
"""
|
164
|
+
Load MEMORY.md from various locations.
|
165
|
+
|
166
|
+
Precedence (highest to lowest):
|
167
|
+
1. Project-specific: ./.claude-mpm/MEMORY.md
|
168
|
+
2. User-specific: ~/.claude-mpm/MEMORY.md
|
169
|
+
3. System default: framework/agents/MEMORY.md
|
170
|
+
|
171
|
+
Args:
|
172
|
+
current_dir: Current working directory
|
173
|
+
framework_path: Path to framework installation
|
174
|
+
|
175
|
+
Returns:
|
176
|
+
Tuple of (content, level) where level is 'project', 'user', 'system', or None
|
177
|
+
"""
|
178
|
+
# Check for project-specific MEMORY.md first (highest priority)
|
179
|
+
project_memory_path = current_dir / ".claude-mpm" / "MEMORY.md"
|
180
|
+
if project_memory_path.exists():
|
181
|
+
loaded_content = self.try_load_file(
|
182
|
+
project_memory_path, "project-specific MEMORY.md"
|
183
|
+
)
|
184
|
+
if loaded_content:
|
185
|
+
self.logger.info(
|
186
|
+
"Using project-specific memory instructions from .claude-mpm/MEMORY.md"
|
187
|
+
)
|
188
|
+
return loaded_content, "project"
|
189
|
+
|
190
|
+
# Check for user-specific MEMORY.md (medium priority)
|
191
|
+
user_memory_path = Path.home() / ".claude-mpm" / "MEMORY.md"
|
192
|
+
if user_memory_path.exists():
|
193
|
+
loaded_content = self.try_load_file(
|
194
|
+
user_memory_path, "user-specific MEMORY.md"
|
195
|
+
)
|
196
|
+
if loaded_content:
|
197
|
+
self.logger.info(
|
198
|
+
"Using user-specific memory instructions from ~/.claude-mpm/MEMORY.md"
|
199
|
+
)
|
200
|
+
return loaded_content, "user"
|
201
|
+
|
202
|
+
# Fall back to system memory instructions (lowest priority)
|
203
|
+
if framework_path and framework_path != Path("__PACKAGED__"):
|
204
|
+
system_memory_path = framework_path / "src" / "claude_mpm" / "agents" / "MEMORY.md"
|
205
|
+
if system_memory_path.exists():
|
206
|
+
loaded_content = self.try_load_file(
|
207
|
+
system_memory_path, "system MEMORY.md"
|
208
|
+
)
|
209
|
+
if loaded_content:
|
210
|
+
self.logger.info("Using system memory instructions")
|
211
|
+
return loaded_content, "system"
|
212
|
+
|
213
|
+
return None, None
|
@@ -0,0 +1,151 @@
|
|
1
|
+
"""Loader for framework instructions and configuration files."""
|
2
|
+
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Any, Dict, Optional
|
5
|
+
|
6
|
+
from claude_mpm.core.logging_utils import get_logger
|
7
|
+
|
8
|
+
from .file_loader import FileLoader
|
9
|
+
from .packaged_loader import PackagedLoader
|
10
|
+
|
11
|
+
|
12
|
+
class InstructionLoader:
|
13
|
+
"""Handles loading of INSTRUCTIONS, WORKFLOW, and MEMORY files."""
|
14
|
+
|
15
|
+
def __init__(self, framework_path: Optional[Path] = None):
|
16
|
+
"""Initialize the instruction loader.
|
17
|
+
|
18
|
+
Args:
|
19
|
+
framework_path: Path to framework installation
|
20
|
+
"""
|
21
|
+
self.logger = get_logger("instruction_loader")
|
22
|
+
self.framework_path = framework_path
|
23
|
+
self.file_loader = FileLoader()
|
24
|
+
self.packaged_loader = PackagedLoader()
|
25
|
+
self.current_dir = Path.cwd()
|
26
|
+
|
27
|
+
def load_all_instructions(self, content: Dict[str, Any]) -> None:
|
28
|
+
"""Load all instruction files into the content dictionary.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
content: Dictionary to update with loaded instructions
|
32
|
+
"""
|
33
|
+
# Load custom INSTRUCTIONS.md
|
34
|
+
self.load_custom_instructions(content)
|
35
|
+
|
36
|
+
# Load framework instructions
|
37
|
+
self.load_framework_instructions(content)
|
38
|
+
|
39
|
+
# Load WORKFLOW.md
|
40
|
+
self.load_workflow_instructions(content)
|
41
|
+
|
42
|
+
# Load MEMORY.md
|
43
|
+
self.load_memory_instructions(content)
|
44
|
+
|
45
|
+
def load_custom_instructions(self, content: Dict[str, Any]) -> None:
|
46
|
+
"""Load custom INSTRUCTIONS.md from .claude-mpm directories.
|
47
|
+
|
48
|
+
Args:
|
49
|
+
content: Dictionary to update with loaded instructions
|
50
|
+
"""
|
51
|
+
instructions, level = self.file_loader.load_instructions_file(self.current_dir)
|
52
|
+
if instructions:
|
53
|
+
content["custom_instructions"] = instructions
|
54
|
+
content["custom_instructions_level"] = level
|
55
|
+
|
56
|
+
def load_framework_instructions(self, content: Dict[str, Any]) -> None:
|
57
|
+
"""Load framework INSTRUCTIONS.md or PM_INSTRUCTIONS.md.
|
58
|
+
|
59
|
+
Args:
|
60
|
+
content: Dictionary to update with framework instructions
|
61
|
+
"""
|
62
|
+
if not self.framework_path:
|
63
|
+
return
|
64
|
+
|
65
|
+
# Check if this is a packaged installation
|
66
|
+
if self.framework_path == Path("__PACKAGED__"):
|
67
|
+
# Use packaged loader
|
68
|
+
self.packaged_loader.load_framework_content(content)
|
69
|
+
else:
|
70
|
+
# Load from filesystem for development mode
|
71
|
+
self._load_filesystem_framework_instructions(content)
|
72
|
+
|
73
|
+
# Update framework metadata
|
74
|
+
if self.file_loader.framework_version:
|
75
|
+
content["instructions_version"] = self.file_loader.framework_version
|
76
|
+
content["version"] = self.file_loader.framework_version
|
77
|
+
if self.file_loader.framework_last_modified:
|
78
|
+
content["instructions_last_modified"] = self.file_loader.framework_last_modified
|
79
|
+
|
80
|
+
# Transfer metadata from packaged loader if available
|
81
|
+
if self.packaged_loader.framework_version:
|
82
|
+
content["instructions_version"] = self.packaged_loader.framework_version
|
83
|
+
content["version"] = self.packaged_loader.framework_version
|
84
|
+
if self.packaged_loader.framework_last_modified:
|
85
|
+
content["instructions_last_modified"] = self.packaged_loader.framework_last_modified
|
86
|
+
|
87
|
+
def _load_filesystem_framework_instructions(self, content: Dict[str, Any]) -> None:
|
88
|
+
"""Load framework instructions from filesystem.
|
89
|
+
|
90
|
+
Args:
|
91
|
+
content: Dictionary to update with framework instructions
|
92
|
+
"""
|
93
|
+
# Try new consolidated PM_INSTRUCTIONS.md first, fall back to INSTRUCTIONS.md
|
94
|
+
pm_instructions_path = (
|
95
|
+
self.framework_path / "src" / "claude_mpm" / "agents" / "PM_INSTRUCTIONS.md"
|
96
|
+
)
|
97
|
+
framework_instructions_path = (
|
98
|
+
self.framework_path / "src" / "claude_mpm" / "agents" / "INSTRUCTIONS.md"
|
99
|
+
)
|
100
|
+
|
101
|
+
# Try loading new consolidated file first
|
102
|
+
if pm_instructions_path.exists():
|
103
|
+
loaded_content = self.file_loader.try_load_file(
|
104
|
+
pm_instructions_path, "consolidated PM_INSTRUCTIONS.md"
|
105
|
+
)
|
106
|
+
if loaded_content:
|
107
|
+
content["framework_instructions"] = loaded_content
|
108
|
+
content["loaded"] = True
|
109
|
+
self.logger.info("Loaded consolidated PM_INSTRUCTIONS.md")
|
110
|
+
# Fall back to legacy file for backward compatibility
|
111
|
+
elif framework_instructions_path.exists():
|
112
|
+
loaded_content = self.file_loader.try_load_file(
|
113
|
+
framework_instructions_path, "framework INSTRUCTIONS.md (legacy)"
|
114
|
+
)
|
115
|
+
if loaded_content:
|
116
|
+
content["framework_instructions"] = loaded_content
|
117
|
+
content["loaded"] = True
|
118
|
+
self.logger.warning(
|
119
|
+
"Using legacy INSTRUCTIONS.md - consider migrating to PM_INSTRUCTIONS.md"
|
120
|
+
)
|
121
|
+
|
122
|
+
# Load BASE_PM.md for core framework requirements
|
123
|
+
base_pm_path = self.framework_path / "src" / "claude_mpm" / "agents" / "BASE_PM.md"
|
124
|
+
if base_pm_path.exists():
|
125
|
+
base_pm_content = self.file_loader.try_load_file(
|
126
|
+
base_pm_path, "BASE_PM framework requirements"
|
127
|
+
)
|
128
|
+
if base_pm_content:
|
129
|
+
content["base_pm_instructions"] = base_pm_content
|
130
|
+
|
131
|
+
def load_workflow_instructions(self, content: Dict[str, Any]) -> None:
|
132
|
+
"""Load WORKFLOW.md from appropriate location.
|
133
|
+
|
134
|
+
Args:
|
135
|
+
content: Dictionary to update with workflow instructions
|
136
|
+
"""
|
137
|
+
workflow, level = self.file_loader.load_workflow_file(self.current_dir, self.framework_path)
|
138
|
+
if workflow:
|
139
|
+
content["workflow_instructions"] = workflow
|
140
|
+
content["workflow_instructions_level"] = level
|
141
|
+
|
142
|
+
def load_memory_instructions(self, content: Dict[str, Any]) -> None:
|
143
|
+
"""Load MEMORY.md from appropriate location.
|
144
|
+
|
145
|
+
Args:
|
146
|
+
content: Dictionary to update with memory instructions
|
147
|
+
"""
|
148
|
+
memory, level = self.file_loader.load_memory_file(self.current_dir, self.framework_path)
|
149
|
+
if memory:
|
150
|
+
content["memory_instructions"] = memory
|
151
|
+
content["memory_instructions_level"] = level
|
@@ -0,0 +1,208 @@
|
|
1
|
+
"""Loader for packaged installations using importlib.resources."""
|
2
|
+
|
3
|
+
import re
|
4
|
+
from typing import Any, Dict, Optional
|
5
|
+
|
6
|
+
from claude_mpm.core.logging_utils import get_logger
|
7
|
+
|
8
|
+
# Import resource handling for packaged installations
|
9
|
+
try:
|
10
|
+
# Python 3.9+
|
11
|
+
from importlib.resources import files
|
12
|
+
except ImportError:
|
13
|
+
# Python 3.8 fallback
|
14
|
+
try:
|
15
|
+
from importlib_resources import files
|
16
|
+
except ImportError:
|
17
|
+
# Final fallback for development environments
|
18
|
+
files = None
|
19
|
+
|
20
|
+
|
21
|
+
class PackagedLoader:
|
22
|
+
"""Handles loading resources from packaged installations."""
|
23
|
+
|
24
|
+
def __init__(self):
|
25
|
+
"""Initialize the packaged loader."""
|
26
|
+
self.logger = get_logger("packaged_loader")
|
27
|
+
self.framework_version: Optional[str] = None
|
28
|
+
self.framework_last_modified: Optional[str] = None
|
29
|
+
|
30
|
+
def load_packaged_file(self, filename: str) -> Optional[str]:
|
31
|
+
"""Load a file from the packaged installation."""
|
32
|
+
if not files:
|
33
|
+
self.logger.warning("importlib.resources not available")
|
34
|
+
return None
|
35
|
+
|
36
|
+
try:
|
37
|
+
# Use importlib.resources to load file from package
|
38
|
+
agents_package = files("claude_mpm.agents")
|
39
|
+
file_path = agents_package / filename
|
40
|
+
|
41
|
+
if file_path.is_file():
|
42
|
+
content = file_path.read_text()
|
43
|
+
self.logger.info(f"Loaded {filename} from package")
|
44
|
+
return content
|
45
|
+
self.logger.warning(f"File {filename} not found in package")
|
46
|
+
return None
|
47
|
+
except Exception as e:
|
48
|
+
self.logger.error(f"Failed to load {filename} from package: {e}")
|
49
|
+
return None
|
50
|
+
|
51
|
+
def load_packaged_file_fallback(self, filename: str, resources) -> Optional[str]:
|
52
|
+
"""Load a file from the packaged installation using importlib.resources fallback."""
|
53
|
+
try:
|
54
|
+
# Try different resource loading methods
|
55
|
+
try:
|
56
|
+
# Method 1: resources.read_text (Python 3.9+)
|
57
|
+
content = resources.read_text("claude_mpm.agents", filename)
|
58
|
+
self.logger.info(f"Loaded {filename} from package using read_text")
|
59
|
+
return content
|
60
|
+
except AttributeError:
|
61
|
+
# Method 2: resources.files (Python 3.9+)
|
62
|
+
agents_files = resources.files("claude_mpm.agents")
|
63
|
+
file_path = agents_files / filename
|
64
|
+
if file_path.is_file():
|
65
|
+
content = file_path.read_text()
|
66
|
+
self.logger.info(f"Loaded {filename} from package using files")
|
67
|
+
return content
|
68
|
+
self.logger.warning(f"File {filename} not found in package")
|
69
|
+
return None
|
70
|
+
except Exception as e:
|
71
|
+
self.logger.error(f"Failed to load {filename} from package with fallback: {e}")
|
72
|
+
return None
|
73
|
+
|
74
|
+
def extract_metadata_from_content(self, content: str, filename: str) -> None:
|
75
|
+
"""Extract metadata from content string.
|
76
|
+
|
77
|
+
Args:
|
78
|
+
content: Content to extract metadata from
|
79
|
+
filename: Filename for context
|
80
|
+
"""
|
81
|
+
# Extract version
|
82
|
+
version_match = re.search(r"<!-- FRAMEWORK_VERSION: (\d+) -->", content)
|
83
|
+
if version_match and "INSTRUCTIONS.md" in filename:
|
84
|
+
self.framework_version = version_match.group(1)
|
85
|
+
self.logger.info(f"Framework version: {self.framework_version}")
|
86
|
+
|
87
|
+
# Extract timestamp
|
88
|
+
timestamp_match = re.search(r"<!-- LAST_MODIFIED: ([^>]+) -->", content)
|
89
|
+
if timestamp_match and "INSTRUCTIONS.md" in filename:
|
90
|
+
self.framework_last_modified = timestamp_match.group(1).strip()
|
91
|
+
self.logger.info(f"Last modified: {self.framework_last_modified}")
|
92
|
+
|
93
|
+
def load_framework_content(self, content: Dict[str, Any]) -> None:
|
94
|
+
"""Load framework content from packaged installation.
|
95
|
+
|
96
|
+
Args:
|
97
|
+
content: Dictionary to update with loaded content
|
98
|
+
"""
|
99
|
+
if not files:
|
100
|
+
self.logger.warning("importlib.resources not available, cannot load packaged framework")
|
101
|
+
self.logger.debug(f"files variable is: {files}")
|
102
|
+
# Try alternative import methods
|
103
|
+
try:
|
104
|
+
from importlib import resources
|
105
|
+
|
106
|
+
self.logger.info("Using importlib.resources as fallback")
|
107
|
+
self.load_framework_content_fallback(content, resources)
|
108
|
+
return
|
109
|
+
except ImportError:
|
110
|
+
self.logger.error("No importlib.resources available, using minimal framework")
|
111
|
+
return
|
112
|
+
|
113
|
+
try:
|
114
|
+
# Try new consolidated PM_INSTRUCTIONS.md first
|
115
|
+
pm_instructions_content = self.load_packaged_file("PM_INSTRUCTIONS.md")
|
116
|
+
if pm_instructions_content:
|
117
|
+
content["framework_instructions"] = pm_instructions_content
|
118
|
+
content["loaded"] = True
|
119
|
+
self.logger.info("Loaded consolidated PM_INSTRUCTIONS.md from package")
|
120
|
+
# Extract and store version/timestamp metadata
|
121
|
+
self.extract_metadata_from_content(pm_instructions_content, "PM_INSTRUCTIONS.md")
|
122
|
+
else:
|
123
|
+
# Fall back to legacy INSTRUCTIONS.md
|
124
|
+
instructions_content = self.load_packaged_file("INSTRUCTIONS.md")
|
125
|
+
if instructions_content:
|
126
|
+
content["framework_instructions"] = instructions_content
|
127
|
+
content["loaded"] = True
|
128
|
+
self.logger.warning("Using legacy INSTRUCTIONS.md from package")
|
129
|
+
# Extract and store version/timestamp metadata
|
130
|
+
self.extract_metadata_from_content(instructions_content, "INSTRUCTIONS.md")
|
131
|
+
|
132
|
+
if self.framework_version:
|
133
|
+
content["instructions_version"] = self.framework_version
|
134
|
+
content["version"] = self.framework_version
|
135
|
+
if self.framework_last_modified:
|
136
|
+
content["instructions_last_modified"] = self.framework_last_modified
|
137
|
+
|
138
|
+
# Load BASE_PM.md
|
139
|
+
base_pm_content = self.load_packaged_file("BASE_PM.md")
|
140
|
+
if base_pm_content:
|
141
|
+
content["base_pm_instructions"] = base_pm_content
|
142
|
+
|
143
|
+
# Load WORKFLOW.md
|
144
|
+
workflow_content = self.load_packaged_file("WORKFLOW.md")
|
145
|
+
if workflow_content:
|
146
|
+
content["workflow_instructions"] = workflow_content
|
147
|
+
content["workflow_instructions_level"] = "system"
|
148
|
+
|
149
|
+
# Load MEMORY.md
|
150
|
+
memory_content = self.load_packaged_file("MEMORY.md")
|
151
|
+
if memory_content:
|
152
|
+
content["memory_instructions"] = memory_content
|
153
|
+
content["memory_instructions_level"] = "system"
|
154
|
+
|
155
|
+
except Exception as e:
|
156
|
+
self.logger.error(f"Failed to load packaged framework content: {e}")
|
157
|
+
|
158
|
+
def load_framework_content_fallback(self, content: Dict[str, Any], resources) -> None:
|
159
|
+
"""Load framework content using importlib.resources fallback.
|
160
|
+
|
161
|
+
Args:
|
162
|
+
content: Dictionary to update with loaded content
|
163
|
+
resources: The importlib.resources module
|
164
|
+
"""
|
165
|
+
try:
|
166
|
+
# Try new consolidated PM_INSTRUCTIONS.md first
|
167
|
+
pm_instructions_content = self.load_packaged_file_fallback("PM_INSTRUCTIONS.md", resources)
|
168
|
+
if pm_instructions_content:
|
169
|
+
content["framework_instructions"] = pm_instructions_content
|
170
|
+
content["loaded"] = True
|
171
|
+
self.logger.info("Loaded consolidated PM_INSTRUCTIONS.md via fallback")
|
172
|
+
# Extract and store version/timestamp metadata
|
173
|
+
self.extract_metadata_from_content(pm_instructions_content, "PM_INSTRUCTIONS.md")
|
174
|
+
else:
|
175
|
+
# Fall back to legacy INSTRUCTIONS.md
|
176
|
+
instructions_content = self.load_packaged_file_fallback("INSTRUCTIONS.md", resources)
|
177
|
+
if instructions_content:
|
178
|
+
content["framework_instructions"] = instructions_content
|
179
|
+
content["loaded"] = True
|
180
|
+
self.logger.warning("Using legacy INSTRUCTIONS.md via fallback")
|
181
|
+
# Extract and store version/timestamp metadata
|
182
|
+
self.extract_metadata_from_content(instructions_content, "INSTRUCTIONS.md")
|
183
|
+
|
184
|
+
if self.framework_version:
|
185
|
+
content["instructions_version"] = self.framework_version
|
186
|
+
content["version"] = self.framework_version
|
187
|
+
if self.framework_last_modified:
|
188
|
+
content["instructions_last_modified"] = self.framework_last_modified
|
189
|
+
|
190
|
+
# Load BASE_PM.md
|
191
|
+
base_pm_content = self.load_packaged_file_fallback("BASE_PM.md", resources)
|
192
|
+
if base_pm_content:
|
193
|
+
content["base_pm_instructions"] = base_pm_content
|
194
|
+
|
195
|
+
# Load WORKFLOW.md
|
196
|
+
workflow_content = self.load_packaged_file_fallback("WORKFLOW.md", resources)
|
197
|
+
if workflow_content:
|
198
|
+
content["workflow_instructions"] = workflow_content
|
199
|
+
content["workflow_instructions_level"] = "system"
|
200
|
+
|
201
|
+
# Load MEMORY.md
|
202
|
+
memory_content = self.load_packaged_file_fallback("MEMORY.md", resources)
|
203
|
+
if memory_content:
|
204
|
+
content["memory_instructions"] = memory_content
|
205
|
+
content["memory_instructions_level"] = "system"
|
206
|
+
|
207
|
+
except Exception as e:
|
208
|
+
self.logger.error(f"Failed to load packaged framework content with fallback: {e}")
|
@@ -0,0 +1,11 @@
|
|
1
|
+
"""Framework processors for handling metadata, templates, and memory content."""
|
2
|
+
|
3
|
+
from .memory_processor import MemoryProcessor
|
4
|
+
from .metadata_processor import MetadataProcessor
|
5
|
+
from .template_processor import TemplateProcessor
|
6
|
+
|
7
|
+
__all__ = [
|
8
|
+
"MetadataProcessor",
|
9
|
+
"TemplateProcessor",
|
10
|
+
"MemoryProcessor",
|
11
|
+
]
|