claude-mpm 4.4.0__py3-none-any.whl → 4.4.4__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 (129) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/WORKFLOW.md +2 -14
  3. claude_mpm/agents/agent_loader.py +3 -2
  4. claude_mpm/agents/agent_loader_integration.py +2 -1
  5. claude_mpm/agents/async_agent_loader.py +2 -2
  6. claude_mpm/agents/base_agent_loader.py +2 -2
  7. claude_mpm/agents/frontmatter_validator.py +1 -0
  8. claude_mpm/agents/system_agent_config.py +2 -1
  9. claude_mpm/cli/commands/configure.py +2 -29
  10. claude_mpm/cli/commands/doctor.py +44 -5
  11. claude_mpm/cli/commands/mpm_init.py +117 -63
  12. claude_mpm/cli/parsers/configure_parser.py +6 -15
  13. claude_mpm/cli/startup_logging.py +1 -3
  14. claude_mpm/config/agent_config.py +1 -1
  15. claude_mpm/config/paths.py +2 -1
  16. claude_mpm/core/agent_name_normalizer.py +1 -0
  17. claude_mpm/core/config.py +2 -1
  18. claude_mpm/core/config_aliases.py +2 -1
  19. claude_mpm/core/file_utils.py +0 -1
  20. claude_mpm/core/framework/__init__.py +38 -0
  21. claude_mpm/core/framework/formatters/__init__.py +11 -0
  22. claude_mpm/core/framework/formatters/capability_generator.py +367 -0
  23. claude_mpm/core/framework/formatters/content_formatter.py +288 -0
  24. claude_mpm/core/framework/formatters/context_generator.py +184 -0
  25. claude_mpm/core/framework/loaders/__init__.py +13 -0
  26. claude_mpm/core/framework/loaders/agent_loader.py +206 -0
  27. claude_mpm/core/framework/loaders/file_loader.py +223 -0
  28. claude_mpm/core/framework/loaders/instruction_loader.py +161 -0
  29. claude_mpm/core/framework/loaders/packaged_loader.py +232 -0
  30. claude_mpm/core/framework/processors/__init__.py +11 -0
  31. claude_mpm/core/framework/processors/memory_processor.py +230 -0
  32. claude_mpm/core/framework/processors/metadata_processor.py +146 -0
  33. claude_mpm/core/framework/processors/template_processor.py +244 -0
  34. claude_mpm/core/framework_loader.py +298 -1795
  35. claude_mpm/core/log_manager.py +2 -1
  36. claude_mpm/core/tool_access_control.py +1 -0
  37. claude_mpm/core/unified_agent_registry.py +2 -1
  38. claude_mpm/core/unified_paths.py +1 -0
  39. claude_mpm/experimental/cli_enhancements.py +1 -0
  40. claude_mpm/hooks/__init__.py +9 -1
  41. claude_mpm/hooks/base_hook.py +1 -0
  42. claude_mpm/hooks/instruction_reinforcement.py +1 -0
  43. claude_mpm/hooks/kuzu_memory_hook.py +359 -0
  44. claude_mpm/hooks/validation_hooks.py +1 -1
  45. claude_mpm/scripts/mpm_doctor.py +1 -0
  46. claude_mpm/services/agents/loading/agent_profile_loader.py +1 -1
  47. claude_mpm/services/agents/loading/base_agent_manager.py +1 -1
  48. claude_mpm/services/agents/loading/framework_agent_loader.py +1 -1
  49. claude_mpm/services/agents/management/agent_capabilities_generator.py +1 -0
  50. claude_mpm/services/agents/management/agent_management_service.py +1 -1
  51. claude_mpm/services/agents/memory/memory_categorization_service.py +0 -1
  52. claude_mpm/services/agents/memory/memory_file_service.py +6 -2
  53. claude_mpm/services/agents/memory/memory_format_service.py +0 -1
  54. claude_mpm/services/agents/registry/deployed_agent_discovery.py +1 -1
  55. claude_mpm/services/async_session_logger.py +1 -1
  56. claude_mpm/services/claude_session_logger.py +1 -0
  57. claude_mpm/services/core/path_resolver.py +2 -0
  58. claude_mpm/services/diagnostics/checks/__init__.py +2 -0
  59. claude_mpm/services/diagnostics/checks/installation_check.py +126 -25
  60. claude_mpm/services/diagnostics/checks/mcp_services_check.py +399 -0
  61. claude_mpm/services/diagnostics/diagnostic_runner.py +4 -0
  62. claude_mpm/services/diagnostics/doctor_reporter.py +259 -32
  63. claude_mpm/services/event_bus/direct_relay.py +2 -1
  64. claude_mpm/services/event_bus/event_bus.py +1 -0
  65. claude_mpm/services/event_bus/relay.py +3 -2
  66. claude_mpm/services/framework_claude_md_generator/content_assembler.py +1 -1
  67. claude_mpm/services/infrastructure/daemon_manager.py +1 -1
  68. claude_mpm/services/mcp_config_manager.py +67 -4
  69. claude_mpm/services/mcp_gateway/core/process_pool.py +320 -0
  70. claude_mpm/services/mcp_gateway/core/startup_verification.py +2 -2
  71. claude_mpm/services/mcp_gateway/main.py +3 -13
  72. claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -10
  73. claude_mpm/services/mcp_gateway/tools/__init__.py +14 -2
  74. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +38 -6
  75. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +527 -0
  76. claude_mpm/services/memory/cache/simple_cache.py +1 -1
  77. claude_mpm/services/project/archive_manager.py +159 -96
  78. claude_mpm/services/project/documentation_manager.py +64 -45
  79. claude_mpm/services/project/enhanced_analyzer.py +132 -89
  80. claude_mpm/services/project/project_organizer.py +225 -131
  81. claude_mpm/services/response_tracker.py +1 -1
  82. claude_mpm/services/shared/__init__.py +2 -1
  83. claude_mpm/services/shared/service_factory.py +8 -5
  84. claude_mpm/services/socketio/server/eventbus_integration.py +1 -1
  85. claude_mpm/services/unified/__init__.py +1 -1
  86. claude_mpm/services/unified/analyzer_strategies/__init__.py +3 -3
  87. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +97 -53
  88. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +81 -40
  89. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +277 -178
  90. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +196 -112
  91. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +83 -49
  92. claude_mpm/services/unified/config_strategies/__init__.py +175 -0
  93. claude_mpm/services/unified/config_strategies/config_schema.py +735 -0
  94. claude_mpm/services/unified/config_strategies/context_strategy.py +750 -0
  95. claude_mpm/services/unified/config_strategies/error_handling_strategy.py +1009 -0
  96. claude_mpm/services/unified/config_strategies/file_loader_strategy.py +879 -0
  97. claude_mpm/services/unified/config_strategies/unified_config_service.py +814 -0
  98. claude_mpm/services/unified/config_strategies/validation_strategy.py +1144 -0
  99. claude_mpm/services/unified/deployment_strategies/__init__.py +7 -7
  100. claude_mpm/services/unified/deployment_strategies/base.py +24 -28
  101. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +168 -88
  102. claude_mpm/services/unified/deployment_strategies/local.py +49 -34
  103. claude_mpm/services/unified/deployment_strategies/utils.py +39 -43
  104. claude_mpm/services/unified/deployment_strategies/vercel.py +30 -24
  105. claude_mpm/services/unified/interfaces.py +0 -26
  106. claude_mpm/services/unified/migration.py +17 -40
  107. claude_mpm/services/unified/strategies.py +9 -26
  108. claude_mpm/services/unified/unified_analyzer.py +48 -44
  109. claude_mpm/services/unified/unified_config.py +21 -19
  110. claude_mpm/services/unified/unified_deployment.py +21 -26
  111. claude_mpm/storage/state_storage.py +1 -0
  112. claude_mpm/utils/agent_dependency_loader.py +18 -6
  113. claude_mpm/utils/common.py +14 -12
  114. claude_mpm/utils/database_connector.py +15 -12
  115. claude_mpm/utils/error_handler.py +1 -0
  116. claude_mpm/utils/log_cleanup.py +1 -0
  117. claude_mpm/utils/path_operations.py +1 -0
  118. claude_mpm/utils/session_logging.py +1 -1
  119. claude_mpm/utils/subprocess_utils.py +1 -0
  120. claude_mpm/validation/agent_validator.py +1 -1
  121. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/METADATA +23 -17
  122. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/RECORD +126 -105
  123. claude_mpm/cli/commands/configure_tui.py +0 -1927
  124. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +0 -645
  125. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +0 -602
  126. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/WHEEL +0 -0
  127. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/entry_points.txt +0 -0
  128. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/licenses/LICENSE +0 -0
  129. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,223 @@
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(
70
+ self, current_dir: Path
71
+ ) -> tuple[Optional[str], Optional[str]]:
72
+ """
73
+ Load custom INSTRUCTIONS.md from .claude-mpm directories.
74
+
75
+ Precedence (highest to lowest):
76
+ 1. Project-specific: ./.claude-mpm/INSTRUCTIONS.md
77
+ 2. User-specific: ~/.claude-mpm/INSTRUCTIONS.md
78
+
79
+ Args:
80
+ current_dir: Current working directory
81
+
82
+ Returns:
83
+ Tuple of (content, level) where level is 'project', 'user', or None
84
+ """
85
+ # Check for project-specific INSTRUCTIONS.md first
86
+ project_instructions_path = current_dir / ".claude-mpm" / "INSTRUCTIONS.md"
87
+ if project_instructions_path.exists():
88
+ loaded_content = self.try_load_file(
89
+ project_instructions_path, "project-specific INSTRUCTIONS.md"
90
+ )
91
+ if loaded_content:
92
+ self.logger.info(
93
+ "Using project-specific PM instructions from .claude-mpm/INSTRUCTIONS.md"
94
+ )
95
+ return loaded_content, "project"
96
+
97
+ # Check for user-specific INSTRUCTIONS.md
98
+ user_instructions_path = Path.home() / ".claude-mpm" / "INSTRUCTIONS.md"
99
+ if user_instructions_path.exists():
100
+ loaded_content = self.try_load_file(
101
+ user_instructions_path, "user-specific INSTRUCTIONS.md"
102
+ )
103
+ if loaded_content:
104
+ self.logger.info(
105
+ "Using user-specific PM instructions from ~/.claude-mpm/INSTRUCTIONS.md"
106
+ )
107
+ return loaded_content, "user"
108
+
109
+ return None, None
110
+
111
+ def load_workflow_file(
112
+ self, current_dir: Path, framework_path: Path
113
+ ) -> tuple[Optional[str], Optional[str]]:
114
+ """
115
+ Load WORKFLOW.md from various locations.
116
+
117
+ Precedence (highest to lowest):
118
+ 1. Project-specific: ./.claude-mpm/WORKFLOW.md
119
+ 2. User-specific: ~/.claude-mpm/WORKFLOW.md
120
+ 3. System default: framework/agents/WORKFLOW.md
121
+
122
+ Args:
123
+ current_dir: Current working directory
124
+ framework_path: Path to framework installation
125
+
126
+ Returns:
127
+ Tuple of (content, level) where level is 'project', 'user', 'system', or None
128
+ """
129
+ # Check for project-specific WORKFLOW.md first (highest priority)
130
+ project_workflow_path = current_dir / ".claude-mpm" / "WORKFLOW.md"
131
+ if project_workflow_path.exists():
132
+ loaded_content = self.try_load_file(
133
+ project_workflow_path, "project-specific WORKFLOW.md"
134
+ )
135
+ if loaded_content:
136
+ self.logger.info(
137
+ "Using project-specific workflow instructions from .claude-mpm/WORKFLOW.md"
138
+ )
139
+ return loaded_content, "project"
140
+
141
+ # Check for user-specific WORKFLOW.md (medium priority)
142
+ user_workflow_path = Path.home() / ".claude-mpm" / "WORKFLOW.md"
143
+ if user_workflow_path.exists():
144
+ loaded_content = self.try_load_file(
145
+ user_workflow_path, "user-specific WORKFLOW.md"
146
+ )
147
+ if loaded_content:
148
+ self.logger.info(
149
+ "Using user-specific workflow instructions from ~/.claude-mpm/WORKFLOW.md"
150
+ )
151
+ return loaded_content, "user"
152
+
153
+ # Fall back to system workflow (lowest priority)
154
+ if framework_path and framework_path != Path("__PACKAGED__"):
155
+ system_workflow_path = (
156
+ framework_path / "src" / "claude_mpm" / "agents" / "WORKFLOW.md"
157
+ )
158
+ if system_workflow_path.exists():
159
+ loaded_content = self.try_load_file(
160
+ system_workflow_path, "system WORKFLOW.md"
161
+ )
162
+ if loaded_content:
163
+ self.logger.info("Using system workflow instructions")
164
+ return loaded_content, "system"
165
+
166
+ return None, None
167
+
168
+ def load_memory_file(
169
+ self, current_dir: Path, framework_path: Path
170
+ ) -> tuple[Optional[str], Optional[str]]:
171
+ """
172
+ Load MEMORY.md from various locations.
173
+
174
+ Precedence (highest to lowest):
175
+ 1. Project-specific: ./.claude-mpm/MEMORY.md
176
+ 2. User-specific: ~/.claude-mpm/MEMORY.md
177
+ 3. System default: framework/agents/MEMORY.md
178
+
179
+ Args:
180
+ current_dir: Current working directory
181
+ framework_path: Path to framework installation
182
+
183
+ Returns:
184
+ Tuple of (content, level) where level is 'project', 'user', 'system', or None
185
+ """
186
+ # Check for project-specific MEMORY.md first (highest priority)
187
+ project_memory_path = current_dir / ".claude-mpm" / "MEMORY.md"
188
+ if project_memory_path.exists():
189
+ loaded_content = self.try_load_file(
190
+ project_memory_path, "project-specific MEMORY.md"
191
+ )
192
+ if loaded_content:
193
+ self.logger.info(
194
+ "Using project-specific memory instructions from .claude-mpm/MEMORY.md"
195
+ )
196
+ return loaded_content, "project"
197
+
198
+ # Check for user-specific MEMORY.md (medium priority)
199
+ user_memory_path = Path.home() / ".claude-mpm" / "MEMORY.md"
200
+ if user_memory_path.exists():
201
+ loaded_content = self.try_load_file(
202
+ user_memory_path, "user-specific MEMORY.md"
203
+ )
204
+ if loaded_content:
205
+ self.logger.info(
206
+ "Using user-specific memory instructions from ~/.claude-mpm/MEMORY.md"
207
+ )
208
+ return loaded_content, "user"
209
+
210
+ # Fall back to system memory instructions (lowest priority)
211
+ if framework_path and framework_path != Path("__PACKAGED__"):
212
+ system_memory_path = (
213
+ framework_path / "src" / "claude_mpm" / "agents" / "MEMORY.md"
214
+ )
215
+ if system_memory_path.exists():
216
+ loaded_content = self.try_load_file(
217
+ system_memory_path, "system MEMORY.md"
218
+ )
219
+ if loaded_content:
220
+ self.logger.info("Using system memory instructions")
221
+ return loaded_content, "system"
222
+
223
+ return None, None
@@ -0,0 +1,161 @@
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"] = (
79
+ self.file_loader.framework_last_modified
80
+ )
81
+
82
+ # Transfer metadata from packaged loader if available
83
+ if self.packaged_loader.framework_version:
84
+ content["instructions_version"] = self.packaged_loader.framework_version
85
+ content["version"] = self.packaged_loader.framework_version
86
+ if self.packaged_loader.framework_last_modified:
87
+ content["instructions_last_modified"] = (
88
+ self.packaged_loader.framework_last_modified
89
+ )
90
+
91
+ def _load_filesystem_framework_instructions(self, content: Dict[str, Any]) -> None:
92
+ """Load framework instructions from filesystem.
93
+
94
+ Args:
95
+ content: Dictionary to update with framework instructions
96
+ """
97
+ # Try new consolidated PM_INSTRUCTIONS.md first, fall back to INSTRUCTIONS.md
98
+ pm_instructions_path = (
99
+ self.framework_path / "src" / "claude_mpm" / "agents" / "PM_INSTRUCTIONS.md"
100
+ )
101
+ framework_instructions_path = (
102
+ self.framework_path / "src" / "claude_mpm" / "agents" / "INSTRUCTIONS.md"
103
+ )
104
+
105
+ # Try loading new consolidated file first
106
+ if pm_instructions_path.exists():
107
+ loaded_content = self.file_loader.try_load_file(
108
+ pm_instructions_path, "consolidated PM_INSTRUCTIONS.md"
109
+ )
110
+ if loaded_content:
111
+ content["framework_instructions"] = loaded_content
112
+ content["loaded"] = True
113
+ self.logger.info("Loaded consolidated PM_INSTRUCTIONS.md")
114
+ # Fall back to legacy file for backward compatibility
115
+ elif framework_instructions_path.exists():
116
+ loaded_content = self.file_loader.try_load_file(
117
+ framework_instructions_path, "framework INSTRUCTIONS.md (legacy)"
118
+ )
119
+ if loaded_content:
120
+ content["framework_instructions"] = loaded_content
121
+ content["loaded"] = True
122
+ self.logger.warning(
123
+ "Using legacy INSTRUCTIONS.md - consider migrating to PM_INSTRUCTIONS.md"
124
+ )
125
+
126
+ # Load BASE_PM.md for core framework requirements
127
+ base_pm_path = (
128
+ self.framework_path / "src" / "claude_mpm" / "agents" / "BASE_PM.md"
129
+ )
130
+ if base_pm_path.exists():
131
+ base_pm_content = self.file_loader.try_load_file(
132
+ base_pm_path, "BASE_PM framework requirements"
133
+ )
134
+ if base_pm_content:
135
+ content["base_pm_instructions"] = base_pm_content
136
+
137
+ def load_workflow_instructions(self, content: Dict[str, Any]) -> None:
138
+ """Load WORKFLOW.md from appropriate location.
139
+
140
+ Args:
141
+ content: Dictionary to update with workflow instructions
142
+ """
143
+ workflow, level = self.file_loader.load_workflow_file(
144
+ self.current_dir, self.framework_path
145
+ )
146
+ if workflow:
147
+ content["workflow_instructions"] = workflow
148
+ content["workflow_instructions_level"] = level
149
+
150
+ def load_memory_instructions(self, content: Dict[str, Any]) -> None:
151
+ """Load MEMORY.md from appropriate location.
152
+
153
+ Args:
154
+ content: Dictionary to update with memory instructions
155
+ """
156
+ memory, level = self.file_loader.load_memory_file(
157
+ self.current_dir, self.framework_path
158
+ )
159
+ if memory:
160
+ content["memory_instructions"] = memory
161
+ content["memory_instructions_level"] = level
@@ -0,0 +1,232 @@
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(
72
+ f"Failed to load {filename} from package with fallback: {e}"
73
+ )
74
+ return None
75
+
76
+ def extract_metadata_from_content(self, content: str, filename: str) -> None:
77
+ """Extract metadata from content string.
78
+
79
+ Args:
80
+ content: Content to extract metadata from
81
+ filename: Filename for context
82
+ """
83
+ # Extract version
84
+ version_match = re.search(r"<!-- FRAMEWORK_VERSION: (\d+) -->", content)
85
+ if version_match and "INSTRUCTIONS.md" in filename:
86
+ self.framework_version = version_match.group(1)
87
+ self.logger.info(f"Framework version: {self.framework_version}")
88
+
89
+ # Extract timestamp
90
+ timestamp_match = re.search(r"<!-- LAST_MODIFIED: ([^>]+) -->", content)
91
+ if timestamp_match and "INSTRUCTIONS.md" in filename:
92
+ self.framework_last_modified = timestamp_match.group(1).strip()
93
+ self.logger.info(f"Last modified: {self.framework_last_modified}")
94
+
95
+ def load_framework_content(self, content: Dict[str, Any]) -> None:
96
+ """Load framework content from packaged installation.
97
+
98
+ Args:
99
+ content: Dictionary to update with loaded content
100
+ """
101
+ if not files:
102
+ self.logger.warning(
103
+ "importlib.resources not available, cannot load packaged framework"
104
+ )
105
+ self.logger.debug(f"files variable is: {files}")
106
+ # Try alternative import methods
107
+ try:
108
+ from importlib import resources
109
+
110
+ self.logger.info("Using importlib.resources as fallback")
111
+ self.load_framework_content_fallback(content, resources)
112
+ return
113
+ except ImportError:
114
+ self.logger.error(
115
+ "No importlib.resources available, using minimal framework"
116
+ )
117
+ return
118
+
119
+ try:
120
+ # Try new consolidated PM_INSTRUCTIONS.md first
121
+ pm_instructions_content = self.load_packaged_file("PM_INSTRUCTIONS.md")
122
+ if pm_instructions_content:
123
+ content["framework_instructions"] = pm_instructions_content
124
+ content["loaded"] = True
125
+ self.logger.info("Loaded consolidated PM_INSTRUCTIONS.md from package")
126
+ # Extract and store version/timestamp metadata
127
+ self.extract_metadata_from_content(
128
+ pm_instructions_content, "PM_INSTRUCTIONS.md"
129
+ )
130
+ else:
131
+ # Fall back to legacy INSTRUCTIONS.md
132
+ instructions_content = self.load_packaged_file("INSTRUCTIONS.md")
133
+ if instructions_content:
134
+ content["framework_instructions"] = instructions_content
135
+ content["loaded"] = True
136
+ self.logger.warning("Using legacy INSTRUCTIONS.md from package")
137
+ # Extract and store version/timestamp metadata
138
+ self.extract_metadata_from_content(
139
+ instructions_content, "INSTRUCTIONS.md"
140
+ )
141
+
142
+ if self.framework_version:
143
+ content["instructions_version"] = self.framework_version
144
+ content["version"] = self.framework_version
145
+ if self.framework_last_modified:
146
+ content["instructions_last_modified"] = self.framework_last_modified
147
+
148
+ # Load BASE_PM.md
149
+ base_pm_content = self.load_packaged_file("BASE_PM.md")
150
+ if base_pm_content:
151
+ content["base_pm_instructions"] = base_pm_content
152
+
153
+ # Load WORKFLOW.md
154
+ workflow_content = self.load_packaged_file("WORKFLOW.md")
155
+ if workflow_content:
156
+ content["workflow_instructions"] = workflow_content
157
+ content["workflow_instructions_level"] = "system"
158
+
159
+ # Load MEMORY.md
160
+ memory_content = self.load_packaged_file("MEMORY.md")
161
+ if memory_content:
162
+ content["memory_instructions"] = memory_content
163
+ content["memory_instructions_level"] = "system"
164
+
165
+ except Exception as e:
166
+ self.logger.error(f"Failed to load packaged framework content: {e}")
167
+
168
+ def load_framework_content_fallback(
169
+ self, content: Dict[str, Any], resources
170
+ ) -> None:
171
+ """Load framework content using importlib.resources fallback.
172
+
173
+ Args:
174
+ content: Dictionary to update with loaded content
175
+ resources: The importlib.resources module
176
+ """
177
+ try:
178
+ # Try new consolidated PM_INSTRUCTIONS.md first
179
+ pm_instructions_content = self.load_packaged_file_fallback(
180
+ "PM_INSTRUCTIONS.md", resources
181
+ )
182
+ if pm_instructions_content:
183
+ content["framework_instructions"] = pm_instructions_content
184
+ content["loaded"] = True
185
+ self.logger.info("Loaded consolidated PM_INSTRUCTIONS.md via fallback")
186
+ # Extract and store version/timestamp metadata
187
+ self.extract_metadata_from_content(
188
+ pm_instructions_content, "PM_INSTRUCTIONS.md"
189
+ )
190
+ else:
191
+ # Fall back to legacy INSTRUCTIONS.md
192
+ instructions_content = self.load_packaged_file_fallback(
193
+ "INSTRUCTIONS.md", resources
194
+ )
195
+ if instructions_content:
196
+ content["framework_instructions"] = instructions_content
197
+ content["loaded"] = True
198
+ self.logger.warning("Using legacy INSTRUCTIONS.md via fallback")
199
+ # Extract and store version/timestamp metadata
200
+ self.extract_metadata_from_content(
201
+ instructions_content, "INSTRUCTIONS.md"
202
+ )
203
+
204
+ if self.framework_version:
205
+ content["instructions_version"] = self.framework_version
206
+ content["version"] = self.framework_version
207
+ if self.framework_last_modified:
208
+ content["instructions_last_modified"] = self.framework_last_modified
209
+
210
+ # Load BASE_PM.md
211
+ base_pm_content = self.load_packaged_file_fallback("BASE_PM.md", resources)
212
+ if base_pm_content:
213
+ content["base_pm_instructions"] = base_pm_content
214
+
215
+ # Load WORKFLOW.md
216
+ workflow_content = self.load_packaged_file_fallback(
217
+ "WORKFLOW.md", resources
218
+ )
219
+ if workflow_content:
220
+ content["workflow_instructions"] = workflow_content
221
+ content["workflow_instructions_level"] = "system"
222
+
223
+ # Load MEMORY.md
224
+ memory_content = self.load_packaged_file_fallback("MEMORY.md", resources)
225
+ if memory_content:
226
+ content["memory_instructions"] = memory_content
227
+ content["memory_instructions_level"] = "system"
228
+
229
+ except Exception as e:
230
+ self.logger.error(
231
+ f"Failed to load packaged framework content with fallback: {e}"
232
+ )
@@ -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
+ "MemoryProcessor",
9
+ "MetadataProcessor",
10
+ "TemplateProcessor",
11
+ ]