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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/WORKFLOW.md +2 -14
- claude_mpm/agents/agent_loader.py +3 -2
- claude_mpm/agents/agent_loader_integration.py +2 -1
- claude_mpm/agents/async_agent_loader.py +2 -2
- claude_mpm/agents/base_agent_loader.py +2 -2
- claude_mpm/agents/frontmatter_validator.py +1 -0
- claude_mpm/agents/system_agent_config.py +2 -1
- claude_mpm/cli/commands/configure.py +2 -29
- claude_mpm/cli/commands/doctor.py +44 -5
- claude_mpm/cli/commands/mpm_init.py +117 -63
- claude_mpm/cli/parsers/configure_parser.py +6 -15
- claude_mpm/cli/startup_logging.py +1 -3
- claude_mpm/config/agent_config.py +1 -1
- claude_mpm/config/paths.py +2 -1
- claude_mpm/core/agent_name_normalizer.py +1 -0
- claude_mpm/core/config.py +2 -1
- claude_mpm/core/config_aliases.py +2 -1
- claude_mpm/core/file_utils.py +0 -1
- 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 +367 -0
- claude_mpm/core/framework/formatters/content_formatter.py +288 -0
- claude_mpm/core/framework/formatters/context_generator.py +184 -0
- claude_mpm/core/framework/loaders/__init__.py +13 -0
- claude_mpm/core/framework/loaders/agent_loader.py +206 -0
- claude_mpm/core/framework/loaders/file_loader.py +223 -0
- claude_mpm/core/framework/loaders/instruction_loader.py +161 -0
- claude_mpm/core/framework/loaders/packaged_loader.py +232 -0
- claude_mpm/core/framework/processors/__init__.py +11 -0
- claude_mpm/core/framework/processors/memory_processor.py +230 -0
- claude_mpm/core/framework/processors/metadata_processor.py +146 -0
- claude_mpm/core/framework/processors/template_processor.py +244 -0
- claude_mpm/core/framework_loader.py +298 -1795
- claude_mpm/core/log_manager.py +2 -1
- claude_mpm/core/tool_access_control.py +1 -0
- claude_mpm/core/unified_agent_registry.py +2 -1
- claude_mpm/core/unified_paths.py +1 -0
- claude_mpm/experimental/cli_enhancements.py +1 -0
- claude_mpm/hooks/__init__.py +9 -1
- claude_mpm/hooks/base_hook.py +1 -0
- claude_mpm/hooks/instruction_reinforcement.py +1 -0
- claude_mpm/hooks/kuzu_memory_hook.py +359 -0
- claude_mpm/hooks/validation_hooks.py +1 -1
- claude_mpm/scripts/mpm_doctor.py +1 -0
- claude_mpm/services/agents/loading/agent_profile_loader.py +1 -1
- claude_mpm/services/agents/loading/base_agent_manager.py +1 -1
- claude_mpm/services/agents/loading/framework_agent_loader.py +1 -1
- claude_mpm/services/agents/management/agent_capabilities_generator.py +1 -0
- claude_mpm/services/agents/management/agent_management_service.py +1 -1
- claude_mpm/services/agents/memory/memory_categorization_service.py +0 -1
- claude_mpm/services/agents/memory/memory_file_service.py +6 -2
- claude_mpm/services/agents/memory/memory_format_service.py +0 -1
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +1 -1
- claude_mpm/services/async_session_logger.py +1 -1
- claude_mpm/services/claude_session_logger.py +1 -0
- claude_mpm/services/core/path_resolver.py +2 -0
- claude_mpm/services/diagnostics/checks/__init__.py +2 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +126 -25
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +399 -0
- claude_mpm/services/diagnostics/diagnostic_runner.py +4 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +259 -32
- claude_mpm/services/event_bus/direct_relay.py +2 -1
- claude_mpm/services/event_bus/event_bus.py +1 -0
- claude_mpm/services/event_bus/relay.py +3 -2
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +1 -1
- claude_mpm/services/infrastructure/daemon_manager.py +1 -1
- claude_mpm/services/mcp_config_manager.py +67 -4
- claude_mpm/services/mcp_gateway/core/process_pool.py +320 -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 +14 -2
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +38 -6
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +527 -0
- claude_mpm/services/memory/cache/simple_cache.py +1 -1
- claude_mpm/services/project/archive_manager.py +159 -96
- claude_mpm/services/project/documentation_manager.py +64 -45
- claude_mpm/services/project/enhanced_analyzer.py +132 -89
- claude_mpm/services/project/project_organizer.py +225 -131
- claude_mpm/services/response_tracker.py +1 -1
- claude_mpm/services/shared/__init__.py +2 -1
- claude_mpm/services/shared/service_factory.py +8 -5
- claude_mpm/services/socketio/server/eventbus_integration.py +1 -1
- claude_mpm/services/unified/__init__.py +1 -1
- claude_mpm/services/unified/analyzer_strategies/__init__.py +3 -3
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +97 -53
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +81 -40
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +277 -178
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +196 -112
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +83 -49
- claude_mpm/services/unified/config_strategies/__init__.py +175 -0
- claude_mpm/services/unified/config_strategies/config_schema.py +735 -0
- claude_mpm/services/unified/config_strategies/context_strategy.py +750 -0
- claude_mpm/services/unified/config_strategies/error_handling_strategy.py +1009 -0
- claude_mpm/services/unified/config_strategies/file_loader_strategy.py +879 -0
- claude_mpm/services/unified/config_strategies/unified_config_service.py +814 -0
- claude_mpm/services/unified/config_strategies/validation_strategy.py +1144 -0
- claude_mpm/services/unified/deployment_strategies/__init__.py +7 -7
- claude_mpm/services/unified/deployment_strategies/base.py +24 -28
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +168 -88
- claude_mpm/services/unified/deployment_strategies/local.py +49 -34
- claude_mpm/services/unified/deployment_strategies/utils.py +39 -43
- claude_mpm/services/unified/deployment_strategies/vercel.py +30 -24
- claude_mpm/services/unified/interfaces.py +0 -26
- claude_mpm/services/unified/migration.py +17 -40
- claude_mpm/services/unified/strategies.py +9 -26
- claude_mpm/services/unified/unified_analyzer.py +48 -44
- claude_mpm/services/unified/unified_config.py +21 -19
- claude_mpm/services/unified/unified_deployment.py +21 -26
- claude_mpm/storage/state_storage.py +1 -0
- claude_mpm/utils/agent_dependency_loader.py +18 -6
- claude_mpm/utils/common.py +14 -12
- claude_mpm/utils/database_connector.py +15 -12
- claude_mpm/utils/error_handler.py +1 -0
- claude_mpm/utils/log_cleanup.py +1 -0
- claude_mpm/utils/path_operations.py +1 -0
- claude_mpm/utils/session_logging.py +1 -1
- claude_mpm/utils/subprocess_utils.py +1 -0
- claude_mpm/validation/agent_validator.py +1 -1
- {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/METADATA +23 -17
- {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/RECORD +126 -105
- 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.4.0.dist-info → claude_mpm-4.4.4.dist-info}/WHEEL +0 -0
- {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/licenses/LICENSE +0 -0
- {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"
|