claude-mpm 4.4.0__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.
Files changed (50) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/WORKFLOW.md +2 -14
  3. claude_mpm/cli/commands/configure.py +2 -29
  4. claude_mpm/cli/commands/mpm_init.py +3 -3
  5. claude_mpm/cli/parsers/configure_parser.py +4 -15
  6. claude_mpm/core/framework/__init__.py +38 -0
  7. claude_mpm/core/framework/formatters/__init__.py +11 -0
  8. claude_mpm/core/framework/formatters/capability_generator.py +356 -0
  9. claude_mpm/core/framework/formatters/content_formatter.py +283 -0
  10. claude_mpm/core/framework/formatters/context_generator.py +180 -0
  11. claude_mpm/core/framework/loaders/__init__.py +13 -0
  12. claude_mpm/core/framework/loaders/agent_loader.py +202 -0
  13. claude_mpm/core/framework/loaders/file_loader.py +213 -0
  14. claude_mpm/core/framework/loaders/instruction_loader.py +151 -0
  15. claude_mpm/core/framework/loaders/packaged_loader.py +208 -0
  16. claude_mpm/core/framework/processors/__init__.py +11 -0
  17. claude_mpm/core/framework/processors/memory_processor.py +222 -0
  18. claude_mpm/core/framework/processors/metadata_processor.py +146 -0
  19. claude_mpm/core/framework/processors/template_processor.py +238 -0
  20. claude_mpm/core/framework_loader.py +277 -1798
  21. claude_mpm/hooks/__init__.py +9 -1
  22. claude_mpm/hooks/kuzu_memory_hook.py +352 -0
  23. claude_mpm/services/core/path_resolver.py +1 -0
  24. claude_mpm/services/diagnostics/diagnostic_runner.py +1 -0
  25. claude_mpm/services/mcp_config_manager.py +67 -4
  26. claude_mpm/services/mcp_gateway/core/process_pool.py +281 -0
  27. claude_mpm/services/mcp_gateway/core/startup_verification.py +2 -2
  28. claude_mpm/services/mcp_gateway/main.py +3 -13
  29. claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -10
  30. claude_mpm/services/mcp_gateway/tools/__init__.py +13 -2
  31. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +36 -6
  32. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +542 -0
  33. claude_mpm/services/shared/__init__.py +2 -1
  34. claude_mpm/services/shared/service_factory.py +8 -5
  35. claude_mpm/services/unified/config_strategies/__init__.py +190 -0
  36. claude_mpm/services/unified/config_strategies/config_schema.py +689 -0
  37. claude_mpm/services/unified/config_strategies/context_strategy.py +748 -0
  38. claude_mpm/services/unified/config_strategies/error_handling_strategy.py +999 -0
  39. claude_mpm/services/unified/config_strategies/file_loader_strategy.py +871 -0
  40. claude_mpm/services/unified/config_strategies/unified_config_service.py +802 -0
  41. claude_mpm/services/unified/config_strategies/validation_strategy.py +1105 -0
  42. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.3.dist-info}/METADATA +15 -15
  43. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.3.dist-info}/RECORD +47 -27
  44. claude_mpm/cli/commands/configure_tui.py +0 -1927
  45. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +0 -645
  46. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +0 -602
  47. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.3.dist-info}/WHEEL +0 -0
  48. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.3.dist-info}/entry_points.txt +0 -0
  49. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.3.dist-info}/licenses/LICENSE +0 -0
  50. {claude_mpm-4.4.0.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
+ ]