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,288 @@
1
+ """Framework content formatter for generating instructions."""
2
+
3
+ import re
4
+ from typing import Any, Dict, Optional
5
+
6
+ from claude_mpm.core.logging_utils import get_logger
7
+
8
+
9
+ class ContentFormatter:
10
+ """Formats framework content for injection into prompts."""
11
+
12
+ def __init__(self):
13
+ """Initialize the content formatter."""
14
+ self.logger = get_logger("content_formatter")
15
+
16
+ def strip_metadata_comments(self, content: str) -> str:
17
+ """Strip metadata HTML comments from content.
18
+
19
+ Removes comments like:
20
+ <!-- FRAMEWORK_VERSION: 0010 -->
21
+ <!-- LAST_MODIFIED: 2025-08-10T00:00:00Z -->
22
+
23
+ Args:
24
+ content: Content to clean
25
+
26
+ Returns:
27
+ Cleaned content without metadata comments
28
+ """
29
+ # Remove HTML comments that contain metadata
30
+ cleaned = re.sub(
31
+ r"<!--\s*(FRAMEWORK_VERSION|LAST_MODIFIED|WORKFLOW_VERSION|PROJECT_WORKFLOW_VERSION|CUSTOM_PROJECT_WORKFLOW)[^>]*-->\n?",
32
+ "",
33
+ content,
34
+ )
35
+ # Also remove any leading blank lines that might result
36
+ return cleaned.lstrip("\n")
37
+
38
+ def format_full_framework(
39
+ self,
40
+ framework_content: Dict[str, Any],
41
+ capabilities_section: str,
42
+ context_section: str,
43
+ inject_output_style: bool = False,
44
+ output_style_content: Optional[str] = None,
45
+ ) -> str:
46
+ """Format complete framework instructions.
47
+
48
+ Args:
49
+ framework_content: Dictionary containing framework content
50
+ capabilities_section: Generated agent capabilities section
51
+ context_section: Generated temporal/user context section
52
+ inject_output_style: Whether to inject output style content
53
+ output_style_content: Output style content to inject (if needed)
54
+
55
+ Returns:
56
+ Formatted framework instructions
57
+ """
58
+ # If we have the full framework INSTRUCTIONS.md, use it
59
+ if framework_content.get("framework_instructions"):
60
+ instructions = self.strip_metadata_comments(
61
+ framework_content["framework_instructions"]
62
+ )
63
+
64
+ # Add custom INSTRUCTIONS.md if present (overrides or extends framework instructions)
65
+ if framework_content.get("custom_instructions"):
66
+ level = framework_content.get("custom_instructions_level", "unknown")
67
+ instructions += f"\n\n## Custom PM Instructions ({level} level)\n\n"
68
+ instructions += "**The following custom instructions override or extend the framework defaults:**\n\n"
69
+ instructions += self.strip_metadata_comments(
70
+ framework_content["custom_instructions"]
71
+ )
72
+ instructions += "\n"
73
+
74
+ # Add WORKFLOW.md after instructions
75
+ if framework_content.get("workflow_instructions"):
76
+ workflow_content = self.strip_metadata_comments(
77
+ framework_content["workflow_instructions"]
78
+ )
79
+ level = framework_content.get("workflow_instructions_level", "system")
80
+ if level != "system":
81
+ instructions += f"\n\n## Workflow Instructions ({level} level)\n\n"
82
+ instructions += "**The following workflow instructions override system defaults:**\n\n"
83
+ instructions += f"{workflow_content}\n"
84
+
85
+ # Add MEMORY.md after workflow instructions
86
+ if framework_content.get("memory_instructions"):
87
+ memory_content = self.strip_metadata_comments(
88
+ framework_content["memory_instructions"]
89
+ )
90
+ level = framework_content.get("memory_instructions_level", "system")
91
+ if level != "system":
92
+ instructions += f"\n\n## Memory Instructions ({level} level)\n\n"
93
+ instructions += "**The following memory instructions override system defaults:**\n\n"
94
+ instructions += f"{memory_content}\n"
95
+
96
+ # Add actual PM memories after memory instructions
97
+ if framework_content.get("actual_memories"):
98
+ instructions += "\n\n## Current PM Memories\n\n"
99
+ instructions += "**The following are your accumulated memories and knowledge from this project:**\n\n"
100
+ instructions += framework_content["actual_memories"]
101
+ instructions += "\n"
102
+
103
+ # Add agent memories if available
104
+ if framework_content.get("agent_memories"):
105
+ agent_memories = framework_content["agent_memories"]
106
+ if agent_memories:
107
+ instructions += "\n\n## Agent Memories\n\n"
108
+ instructions += "**The following are accumulated memories from specialized agents:**\n\n"
109
+
110
+ for agent_name in sorted(agent_memories.keys()):
111
+ memory_content = agent_memories[agent_name]
112
+ if memory_content:
113
+ instructions += f"### {agent_name.replace('_', ' ').title()} Agent Memory\n\n"
114
+ instructions += memory_content
115
+ instructions += "\n\n"
116
+
117
+ # Add dynamic agent capabilities section
118
+ instructions += capabilities_section
119
+
120
+ # Add enhanced temporal and user context for better awareness
121
+ instructions += context_section
122
+
123
+ # Add BASE_PM.md framework requirements AFTER INSTRUCTIONS.md
124
+ if framework_content.get("base_pm_instructions"):
125
+ base_pm = self.strip_metadata_comments(
126
+ framework_content["base_pm_instructions"]
127
+ )
128
+ instructions += f"\n\n{base_pm}"
129
+
130
+ # Inject output style content if needed (for Claude < 1.0.83)
131
+ if inject_output_style and output_style_content:
132
+ instructions += "\n\n## Output Style Configuration\n"
133
+ instructions += "**Note: The following output style is injected for Claude < 1.0.83**\n\n"
134
+ instructions += output_style_content
135
+ instructions += "\n"
136
+
137
+ # Clean up any trailing whitespace
138
+ return instructions.rstrip() + "\n"
139
+
140
+ # Otherwise generate minimal framework
141
+ return self.format_minimal_framework(framework_content)
142
+
143
+ def format_minimal_framework(self, framework_content: Dict[str, Any]) -> str:
144
+ """Format minimal framework instructions when full framework not available.
145
+
146
+ Args:
147
+ framework_content: Dictionary containing framework content
148
+
149
+ Returns:
150
+ Minimal framework instructions
151
+ """
152
+ instructions = """# Claude MPM Framework Instructions
153
+
154
+ You are operating within the Claude Multi-Agent Project Manager (MPM) framework.
155
+
156
+ ## Core Role
157
+ You are a multi-agent orchestrator. Your primary responsibilities are:
158
+ - Delegate all implementation work to specialized agents via Task Tool
159
+ - Coordinate multi-agent workflows and cross-agent collaboration
160
+ - Extract and track TODO/BUG/FEATURE items for ticket creation
161
+ - Maintain project visibility and strategic oversight
162
+ - NEVER perform direct implementation work yourself
163
+
164
+ """
165
+
166
+ # Add agent definitions if available
167
+ if framework_content.get("agents"):
168
+ instructions += "## Available Agents\n\n"
169
+ instructions += "You have the following specialized agents available for delegation:\n\n"
170
+
171
+ # List agents with brief descriptions and correct IDs
172
+ agent_list = []
173
+ for agent_name in sorted(framework_content["agents"].keys()):
174
+ # Use the actual agent_name as the ID (it's the filename stem)
175
+ agent_id = agent_name
176
+ clean_name = agent_name.replace("-", " ").replace("_", " ").title()
177
+ if (
178
+ "engineer" in agent_name.lower()
179
+ and "data" not in agent_name.lower()
180
+ ):
181
+ agent_list.append(
182
+ f"- **Engineer Agent** (`{agent_id}`): Code implementation and development"
183
+ )
184
+ elif "qa" in agent_name.lower():
185
+ agent_list.append(
186
+ f"- **QA Agent** (`{agent_id}`): Testing and quality assurance"
187
+ )
188
+ elif "documentation" in agent_name.lower():
189
+ agent_list.append(
190
+ f"- **Documentation Agent** (`{agent_id}`): Documentation creation and maintenance"
191
+ )
192
+ elif "research" in agent_name.lower():
193
+ agent_list.append(
194
+ f"- **Research Agent** (`{agent_id}`): Investigation and analysis"
195
+ )
196
+ elif "security" in agent_name.lower():
197
+ agent_list.append(
198
+ f"- **Security Agent** (`{agent_id}`): Security analysis and protection"
199
+ )
200
+ elif "version" in agent_name.lower():
201
+ agent_list.append(
202
+ f"- **Version Control Agent** (`{agent_id}`): Git operations and version management"
203
+ )
204
+ elif "ops" in agent_name.lower():
205
+ agent_list.append(
206
+ f"- **Ops Agent** (`{agent_id}`): Deployment and operations"
207
+ )
208
+ elif "data" in agent_name.lower():
209
+ agent_list.append(
210
+ f"- **Data Engineer Agent** (`{agent_id}`): Data management and AI API integration"
211
+ )
212
+ else:
213
+ agent_list.append(
214
+ f"- **{clean_name}** (`{agent_id}`): Available for specialized tasks"
215
+ )
216
+
217
+ instructions += "\n".join(agent_list) + "\n\n"
218
+
219
+ # Add full agent details
220
+ instructions += "### Agent Details\n\n"
221
+ for agent_name, agent_content in sorted(
222
+ framework_content["agents"].items()
223
+ ):
224
+ instructions += f"#### {agent_name.replace('-', ' ').title()}\n"
225
+ instructions += agent_content + "\n\n"
226
+
227
+ # Add orchestration principles
228
+ instructions += """
229
+ ## Orchestration Principles
230
+ 1. **Always Delegate**: Never perform direct work - use Task Tool for all implementation
231
+ 2. **Comprehensive Context**: Provide rich, filtered context to each agent
232
+ 3. **Track Everything**: Extract all TODO/BUG/FEATURE items systematically
233
+ 4. **Cross-Agent Coordination**: Orchestrate workflows spanning multiple agents
234
+ 5. **Results Integration**: Actively receive and integrate agent results
235
+
236
+ ## Task Tool Format
237
+ ```
238
+ **[Agent Name]**: [Clear task description with deliverables]
239
+
240
+ TEMPORAL CONTEXT: Today is [date]. Apply date awareness to [specific considerations].
241
+
242
+ **Task**: [Detailed task breakdown]
243
+ 1. [Specific action item 1]
244
+ 2. [Specific action item 2]
245
+ 3. [Specific action item 3]
246
+
247
+ **Context**: [Comprehensive filtered context for this agent]
248
+ **Authority**: [Agent's decision-making scope]
249
+ **Expected Results**: [Specific deliverables needed]
250
+ **Integration**: [How results integrate with other work]
251
+ ```
252
+
253
+ ## Ticket Extraction Patterns
254
+ Extract tickets from these patterns:
255
+ - TODO: [description] → TODO ticket
256
+ - BUG: [description] → BUG ticket
257
+ - FEATURE: [description] → FEATURE ticket
258
+ - ISSUE: [description] → ISSUE ticket
259
+ - FIXME: [description] → BUG ticket
260
+
261
+ ---
262
+ """
263
+
264
+ return instructions
265
+
266
+ def get_fallback_capabilities(self) -> str:
267
+ """Return fallback capabilities when dynamic discovery fails.
268
+
269
+ Returns:
270
+ Fallback agent capabilities section
271
+ """
272
+ return """
273
+
274
+ ## Available Agent Capabilities
275
+
276
+ You have the following specialized agents available for delegation:
277
+
278
+ - **Engineer** (`engineer`): Code implementation and development
279
+ - **Research** (`research-agent`): Investigation and analysis
280
+ - **QA** (`qa-agent`): Testing and quality assurance
281
+ - **Documentation** (`documentation-agent`): Documentation creation and maintenance
282
+ - **Security** (`security-agent`): Security analysis and protection
283
+ - **Data Engineer** (`data-engineer`): Data management and pipelines
284
+ - **Ops** (`ops-agent`): Deployment and operations
285
+ - **Version Control** (`version-control`): Git operations and version management
286
+
287
+ **IMPORTANT**: Use the exact agent ID in parentheses when delegating tasks.
288
+ """
@@ -0,0 +1,184 @@
1
+ """Temporal and user context generator for framework instructions."""
2
+
3
+ import getpass
4
+ import locale
5
+ import os
6
+ import platform
7
+ import time as time_module
8
+ from datetime import datetime, timezone
9
+
10
+ from claude_mpm.core.logging_utils import get_logger
11
+
12
+
13
+ class ContextGenerator:
14
+ """Generates temporal and user context for better PM awareness."""
15
+
16
+ def __init__(self):
17
+ """Initialize the context generator."""
18
+ self.logger = get_logger("context_generator")
19
+
20
+ def generate_temporal_user_context(self) -> str:
21
+ """Generate enhanced temporal and user context for better PM awareness.
22
+
23
+ Returns:
24
+ Formatted context string with datetime, user, and system information
25
+ """
26
+ context_lines = ["\n\n## Temporal & User Context\n"]
27
+
28
+ try:
29
+ # Get current datetime with timezone awareness
30
+ now = datetime.now(timezone.utc)
31
+
32
+ # Try to get timezone info - fallback to UTC offset if timezone name not available
33
+ try:
34
+ if hasattr(time_module, "tzname"):
35
+ tz_name = time_module.tzname[time_module.daylight]
36
+ tz_offset = time_module.strftime("%z")
37
+ if tz_offset:
38
+ # Format UTC offset properly (e.g., -0800 to -08:00)
39
+ tz_offset = (
40
+ f"{tz_offset[:3]}:{tz_offset[3:]}"
41
+ if len(tz_offset) >= 4
42
+ else tz_offset
43
+ )
44
+ tz_info = f"{tz_name} (UTC{tz_offset})"
45
+ else:
46
+ tz_info = tz_name
47
+ else:
48
+ tz_info = "Local Time"
49
+ except Exception:
50
+ tz_info = "Local Time"
51
+
52
+ # Format datetime components
53
+ date_str = now.strftime("%Y-%m-%d")
54
+ time_str = now.strftime("%H:%M:%S")
55
+ day_name = now.strftime("%A")
56
+
57
+ context_lines.append(
58
+ f"**Current DateTime**: {date_str} {time_str} {tz_info}\n"
59
+ )
60
+ context_lines.append(f"**Day**: {day_name}\n")
61
+
62
+ except Exception as e:
63
+ # Fallback to basic date if enhanced datetime fails
64
+ self.logger.debug(f"Error generating enhanced datetime context: {e}")
65
+ context_lines.append(
66
+ f"**Today's Date**: {datetime.now(timezone.utc).strftime('%Y-%m-%d')}\n"
67
+ )
68
+
69
+ # Get user information
70
+ self._add_user_context(context_lines)
71
+
72
+ # Get system information
73
+ self._add_system_context(context_lines)
74
+
75
+ # Get environment information
76
+ self._add_environment_context(context_lines)
77
+
78
+ # Add instruction for applying context
79
+ context_lines.append(
80
+ "\nApply temporal and user awareness to all tasks, "
81
+ "decisions, and interactions.\n"
82
+ )
83
+ context_lines.append(
84
+ "Use this context for personalized responses and "
85
+ "time-sensitive operations.\n"
86
+ )
87
+
88
+ return "".join(context_lines)
89
+
90
+ def _add_user_context(self, context_lines: list) -> None:
91
+ """Add user information to context.
92
+
93
+ Args:
94
+ context_lines: List to append context lines to
95
+ """
96
+ try:
97
+ # Get user information with safe fallbacks
98
+ username = None
99
+
100
+ # Try multiple methods to get username
101
+ methods = [
102
+ lambda: os.environ.get("USER"),
103
+ lambda: os.environ.get("USERNAME"), # Windows fallback
104
+ lambda: getpass.getuser(),
105
+ ]
106
+
107
+ for method in methods:
108
+ try:
109
+ username = method()
110
+ if username:
111
+ break
112
+ except Exception:
113
+ continue
114
+
115
+ if username:
116
+ context_lines.append(f"**User**: {username}\n")
117
+
118
+ # Add home directory if available
119
+ try:
120
+ home_dir = os.path.expanduser("~")
121
+ if home_dir and home_dir != "~":
122
+ context_lines.append(f"**Home Directory**: {home_dir}\n")
123
+ except Exception:
124
+ pass
125
+
126
+ except Exception as e:
127
+ # User detection is optional, don't fail
128
+ self.logger.debug(f"Could not detect user information: {e}")
129
+
130
+ def _add_system_context(self, context_lines: list) -> None:
131
+ """Add system information to context.
132
+
133
+ Args:
134
+ context_lines: List to append context lines to
135
+ """
136
+ try:
137
+ # Get system information
138
+ system_info = platform.system()
139
+ if system_info:
140
+ # Enhance system name for common platforms
141
+ system_names = {
142
+ "Darwin": "Darwin (macOS)",
143
+ "Linux": "Linux",
144
+ "Windows": "Windows",
145
+ }
146
+ system_display = system_names.get(system_info, system_info)
147
+ context_lines.append(f"**System**: {system_display}\n")
148
+
149
+ # Add platform version if available
150
+ try:
151
+ platform_version = platform.release()
152
+ if platform_version:
153
+ context_lines.append(
154
+ f"**System Version**: {platform_version}\n"
155
+ )
156
+ except Exception:
157
+ pass
158
+
159
+ except Exception as e:
160
+ # System info is optional
161
+ self.logger.debug(f"Could not detect system information: {e}")
162
+
163
+ def _add_environment_context(self, context_lines: list) -> None:
164
+ """Add environment information to context.
165
+
166
+ Args:
167
+ context_lines: List to append context lines to
168
+ """
169
+ try:
170
+ # Add current working directory
171
+ cwd = os.getcwd()
172
+ if cwd:
173
+ context_lines.append(f"**Working Directory**: {cwd}\n")
174
+ except Exception:
175
+ pass
176
+
177
+ try:
178
+ # Add locale information if available
179
+ current_locale = locale.getlocale()
180
+ if current_locale and current_locale[0]:
181
+ context_lines.append(f"**Locale**: {current_locale[0]}\n")
182
+ except Exception:
183
+ # Locale is optional
184
+ pass
@@ -0,0 +1,13 @@
1
+ """Framework loaders for handling various types of file and content loading."""
2
+
3
+ from .agent_loader import AgentLoader
4
+ from .file_loader import FileLoader
5
+ from .instruction_loader import InstructionLoader
6
+ from .packaged_loader import PackagedLoader
7
+
8
+ __all__ = [
9
+ "AgentLoader",
10
+ "FileLoader",
11
+ "InstructionLoader",
12
+ "PackagedLoader",
13
+ ]
@@ -0,0 +1,206 @@
1
+ """Loader for agent discovery and management."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any, Dict, Optional, Set, Tuple
5
+
6
+ from claude_mpm.core.logging_utils import get_logger
7
+
8
+
9
+ class AgentLoader:
10
+ """Handles agent discovery and loading from various sources."""
11
+
12
+ def __init__(self, framework_path: Optional[Path] = None):
13
+ """Initialize the agent loader.
14
+
15
+ Args:
16
+ framework_path: Path to framework installation
17
+ """
18
+ self.logger = get_logger("agent_loader")
19
+ self.framework_path = framework_path
20
+
21
+ def get_deployed_agents(self) -> Set[str]:
22
+ """
23
+ Get a set of deployed agent names from .claude/agents/ directories.
24
+
25
+ Returns:
26
+ Set of agent names (file stems) that are deployed
27
+ """
28
+ self.logger.debug("Scanning for deployed agents")
29
+ deployed = set()
30
+
31
+ # Check multiple locations for deployed agents
32
+ agents_dirs = [
33
+ Path.cwd() / ".claude" / "agents", # Project-specific agents
34
+ Path.home() / ".claude" / "agents", # User's system agents
35
+ ]
36
+
37
+ for agents_dir in agents_dirs:
38
+ if agents_dir.exists():
39
+ for agent_file in agents_dir.glob("*.md"):
40
+ if not agent_file.name.startswith("."):
41
+ # Use stem to get agent name without extension
42
+ deployed.add(agent_file.stem)
43
+ self.logger.debug(
44
+ f"Found deployed agent: {agent_file.stem} in {agents_dir}"
45
+ )
46
+
47
+ self.logger.debug(f"Total deployed agents found: {len(deployed)}")
48
+ return deployed
49
+
50
+ def load_single_agent(
51
+ self, agent_file: Path
52
+ ) -> Tuple[Optional[str], Optional[str]]:
53
+ """
54
+ Load a single agent file.
55
+
56
+ Args:
57
+ agent_file: Path to the agent file
58
+
59
+ Returns:
60
+ Tuple of (agent_name, agent_content) or (None, None) on failure
61
+ """
62
+ try:
63
+ agent_name = agent_file.stem
64
+ # Skip README files
65
+ if agent_name.upper() == "README":
66
+ return None, None
67
+ content = agent_file.read_text()
68
+ self.logger.debug(f"Loaded agent: {agent_name}")
69
+ return agent_name, content
70
+ except Exception as e:
71
+ self.logger.error(f"Failed to load agent {agent_file}: {e}")
72
+ return None, None
73
+
74
+ def load_agents_directory(
75
+ self,
76
+ agents_dir: Optional[Path],
77
+ templates_dir: Optional[Path] = None,
78
+ main_dir: Optional[Path] = None,
79
+ ) -> Dict[str, str]:
80
+ """
81
+ Load agent definitions from the appropriate directory.
82
+
83
+ Args:
84
+ agents_dir: Primary agents directory to load from
85
+ templates_dir: Templates directory path
86
+ main_dir: Main agents directory path
87
+
88
+ Returns:
89
+ Dictionary mapping agent names to their content
90
+ """
91
+ agents = {}
92
+
93
+ if not agents_dir or not agents_dir.exists():
94
+ return agents
95
+
96
+ # Load all agent files
97
+ for agent_file in agents_dir.glob("*.md"):
98
+ agent_name, agent_content = self.load_single_agent(agent_file)
99
+ if agent_name and agent_content:
100
+ agents[agent_name] = agent_content
101
+
102
+ # If we used templates dir, also check main dir for base_agent.md
103
+ if agents_dir == templates_dir and main_dir and main_dir.exists():
104
+ if "base_agent" not in agents:
105
+ base_agent_file = main_dir / "base_agent.md"
106
+ if base_agent_file.exists():
107
+ agent_name, agent_content = self.load_single_agent(base_agent_file)
108
+ if agent_name and agent_content:
109
+ agents[agent_name] = agent_content
110
+
111
+ return agents
112
+
113
+ def discover_local_json_templates(self) -> Dict[str, Dict[str, Any]]:
114
+ """Discover local JSON agent templates from .claude-mpm/agents/ directories.
115
+
116
+ Returns:
117
+ Dictionary mapping agent IDs to agent metadata
118
+ """
119
+ import json
120
+
121
+ local_agents = {}
122
+
123
+ # Check for local JSON templates in priority order
124
+ template_dirs = [
125
+ Path.cwd()
126
+ / ".claude-mpm"
127
+ / "agents", # Project local agents (highest priority)
128
+ Path.home() / ".claude-mpm" / "agents", # User local agents
129
+ ]
130
+
131
+ for priority, template_dir in enumerate(template_dirs):
132
+ if not template_dir.exists():
133
+ continue
134
+
135
+ for json_file in template_dir.glob("*.json"):
136
+ try:
137
+ with open(json_file) as f:
138
+ template_data = json.load(f)
139
+
140
+ # Extract agent metadata
141
+ agent_id = template_data.get("agent_id", json_file.stem)
142
+
143
+ # Skip if already found at higher priority
144
+ if agent_id in local_agents:
145
+ continue
146
+
147
+ # Extract metadata
148
+ metadata = template_data.get("metadata", {})
149
+
150
+ # Build agent data in expected format
151
+ agent_data = {
152
+ "id": agent_id,
153
+ "display_name": metadata.get(
154
+ "name", agent_id.replace("_", " ").title()
155
+ ),
156
+ "description": metadata.get(
157
+ "description", f"Local {agent_id} agent"
158
+ ),
159
+ "tools": self._extract_tools_from_template(template_data),
160
+ "is_local": True,
161
+ "tier": "project" if priority == 0 else "user",
162
+ "author": template_data.get("author", "local"),
163
+ "version": template_data.get("agent_version", "1.0.0"),
164
+ }
165
+
166
+ # Add routing data if present
167
+ if "routing" in template_data:
168
+ agent_data["routing"] = template_data["routing"]
169
+
170
+ # Add memory routing if present
171
+ if "memory_routing" in template_data:
172
+ agent_data["memory_routing"] = template_data["memory_routing"]
173
+
174
+ local_agents[agent_id] = agent_data
175
+ self.logger.debug(
176
+ f"Discovered local JSON agent: {agent_id} from {template_dir}"
177
+ )
178
+
179
+ except Exception as e:
180
+ self.logger.warning(
181
+ f"Failed to parse local JSON template {json_file}: {e}"
182
+ )
183
+
184
+ return local_agents
185
+
186
+ def _extract_tools_from_template(self, template_data: Dict[str, Any]) -> str:
187
+ """Extract tools string from template data.
188
+
189
+ Args:
190
+ template_data: JSON template data
191
+
192
+ Returns:
193
+ Tools string for display
194
+ """
195
+ capabilities = template_data.get("capabilities", {})
196
+ tools = capabilities.get("tools", "*")
197
+
198
+ if tools == "*":
199
+ return "All Tools"
200
+ if isinstance(tools, list):
201
+ return ", ".join(tools) if tools else "Standard Tools"
202
+ if isinstance(tools, str):
203
+ if "," in tools:
204
+ return tools
205
+ return tools
206
+ return "Standard Tools"