claude-mpm 0.3.0__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.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/__init__.py +17 -0
- claude_mpm/__main__.py +14 -0
- claude_mpm/_version.py +32 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +88 -0
- claude_mpm/agents/INSTRUCTIONS.md +375 -0
- claude_mpm/agents/__init__.py +118 -0
- claude_mpm/agents/agent_loader.py +621 -0
- claude_mpm/agents/agent_loader_integration.py +229 -0
- claude_mpm/agents/agents_metadata.py +204 -0
- claude_mpm/agents/base_agent.json +27 -0
- claude_mpm/agents/base_agent_loader.py +519 -0
- claude_mpm/agents/schema/agent_schema.json +160 -0
- claude_mpm/agents/system_agent_config.py +587 -0
- claude_mpm/agents/templates/__init__.py +101 -0
- claude_mpm/agents/templates/data_engineer_agent.json +46 -0
- claude_mpm/agents/templates/documentation_agent.json +45 -0
- claude_mpm/agents/templates/engineer_agent.json +49 -0
- claude_mpm/agents/templates/ops_agent.json +46 -0
- claude_mpm/agents/templates/qa_agent.json +45 -0
- claude_mpm/agents/templates/research_agent.json +49 -0
- claude_mpm/agents/templates/security_agent.json +46 -0
- claude_mpm/agents/templates/update-optimized-specialized-agents.json +374 -0
- claude_mpm/agents/templates/version_control_agent.json +46 -0
- claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +6 -0
- claude_mpm/cli.py +655 -0
- claude_mpm/cli_main.py +13 -0
- claude_mpm/cli_module/__init__.py +15 -0
- claude_mpm/cli_module/args.py +222 -0
- claude_mpm/cli_module/commands.py +203 -0
- claude_mpm/cli_module/migration_example.py +183 -0
- claude_mpm/cli_module/refactoring_guide.md +253 -0
- claude_mpm/cli_old/__init__.py +1 -0
- claude_mpm/cli_old/ticket_cli.py +102 -0
- claude_mpm/config/__init__.py +5 -0
- claude_mpm/config/hook_config.py +42 -0
- claude_mpm/constants.py +150 -0
- claude_mpm/core/__init__.py +45 -0
- claude_mpm/core/agent_name_normalizer.py +248 -0
- claude_mpm/core/agent_registry.py +627 -0
- claude_mpm/core/agent_registry.py.bak +312 -0
- claude_mpm/core/agent_session_manager.py +273 -0
- claude_mpm/core/base_service.py +747 -0
- claude_mpm/core/base_service.py.bak +406 -0
- claude_mpm/core/config.py +334 -0
- claude_mpm/core/config_aliases.py +292 -0
- claude_mpm/core/container.py +347 -0
- claude_mpm/core/factories.py +281 -0
- claude_mpm/core/framework_loader.py +472 -0
- claude_mpm/core/injectable_service.py +206 -0
- claude_mpm/core/interfaces.py +539 -0
- claude_mpm/core/logger.py +468 -0
- claude_mpm/core/minimal_framework_loader.py +107 -0
- claude_mpm/core/mixins.py +150 -0
- claude_mpm/core/service_registry.py +299 -0
- claude_mpm/core/session_manager.py +190 -0
- claude_mpm/core/simple_runner.py +511 -0
- claude_mpm/core/tool_access_control.py +173 -0
- claude_mpm/hooks/README.md +243 -0
- claude_mpm/hooks/__init__.py +5 -0
- claude_mpm/hooks/base_hook.py +154 -0
- claude_mpm/hooks/builtin/__init__.py +1 -0
- claude_mpm/hooks/builtin/logging_hook_example.py +165 -0
- claude_mpm/hooks/builtin/post_delegation_hook_example.py +124 -0
- claude_mpm/hooks/builtin/pre_delegation_hook_example.py +125 -0
- claude_mpm/hooks/builtin/submit_hook_example.py +100 -0
- claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +237 -0
- claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +239 -0
- claude_mpm/hooks/builtin/workflow_start_hook.py +181 -0
- claude_mpm/hooks/hook_client.py +264 -0
- claude_mpm/hooks/hook_runner.py +370 -0
- claude_mpm/hooks/json_rpc_executor.py +259 -0
- claude_mpm/hooks/json_rpc_hook_client.py +319 -0
- claude_mpm/hooks/tool_call_interceptor.py +204 -0
- claude_mpm/init.py +246 -0
- claude_mpm/orchestration/SUBPROCESS_DESIGN.md +66 -0
- claude_mpm/orchestration/__init__.py +6 -0
- claude_mpm/orchestration/archive/direct_orchestrator.py +195 -0
- claude_mpm/orchestration/archive/factory.py +215 -0
- claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +188 -0
- claude_mpm/orchestration/archive/hook_integration_example.py +178 -0
- claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +826 -0
- claude_mpm/orchestration/archive/orchestrator.py +501 -0
- claude_mpm/orchestration/archive/pexpect_orchestrator.py +252 -0
- claude_mpm/orchestration/archive/pty_orchestrator.py +270 -0
- claude_mpm/orchestration/archive/simple_orchestrator.py +82 -0
- claude_mpm/orchestration/archive/subprocess_orchestrator.py +801 -0
- claude_mpm/orchestration/archive/system_prompt_orchestrator.py +278 -0
- claude_mpm/orchestration/archive/wrapper_orchestrator.py +187 -0
- claude_mpm/scripts/__init__.py +1 -0
- claude_mpm/scripts/ticket.py +269 -0
- claude_mpm/services/__init__.py +10 -0
- claude_mpm/services/agent_deployment.py +955 -0
- claude_mpm/services/agent_lifecycle_manager.py +948 -0
- claude_mpm/services/agent_management_service.py +596 -0
- claude_mpm/services/agent_modification_tracker.py +841 -0
- claude_mpm/services/agent_profile_loader.py +606 -0
- claude_mpm/services/agent_registry.py +677 -0
- claude_mpm/services/base_agent_manager.py +380 -0
- claude_mpm/services/framework_agent_loader.py +337 -0
- claude_mpm/services/framework_claude_md_generator/README.md +92 -0
- claude_mpm/services/framework_claude_md_generator/__init__.py +206 -0
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +151 -0
- claude_mpm/services/framework_claude_md_generator/content_validator.py +126 -0
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +137 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +106 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +582 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +97 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +27 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +23 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +23 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +20 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +26 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +30 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +37 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +111 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +89 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +39 -0
- claude_mpm/services/framework_claude_md_generator/section_manager.py +106 -0
- claude_mpm/services/framework_claude_md_generator/version_manager.py +121 -0
- claude_mpm/services/framework_claude_md_generator.py +621 -0
- claude_mpm/services/hook_service.py +388 -0
- claude_mpm/services/hook_service_manager.py +223 -0
- claude_mpm/services/json_rpc_hook_manager.py +92 -0
- claude_mpm/services/parent_directory_manager/README.md +83 -0
- claude_mpm/services/parent_directory_manager/__init__.py +577 -0
- claude_mpm/services/parent_directory_manager/backup_manager.py +258 -0
- claude_mpm/services/parent_directory_manager/config_manager.py +210 -0
- claude_mpm/services/parent_directory_manager/deduplication_manager.py +279 -0
- claude_mpm/services/parent_directory_manager/framework_protector.py +143 -0
- claude_mpm/services/parent_directory_manager/operations.py +186 -0
- claude_mpm/services/parent_directory_manager/state_manager.py +624 -0
- claude_mpm/services/parent_directory_manager/template_deployer.py +579 -0
- claude_mpm/services/parent_directory_manager/validation_manager.py +378 -0
- claude_mpm/services/parent_directory_manager/version_control_helper.py +339 -0
- claude_mpm/services/parent_directory_manager/version_manager.py +222 -0
- claude_mpm/services/shared_prompt_cache.py +819 -0
- claude_mpm/services/ticket_manager.py +213 -0
- claude_mpm/services/ticket_manager_di.py +318 -0
- claude_mpm/services/ticketing_service_original.py +508 -0
- claude_mpm/services/version_control/VERSION +1 -0
- claude_mpm/services/version_control/__init__.py +70 -0
- claude_mpm/services/version_control/branch_strategy.py +670 -0
- claude_mpm/services/version_control/conflict_resolution.py +744 -0
- claude_mpm/services/version_control/git_operations.py +784 -0
- claude_mpm/services/version_control/semantic_versioning.py +703 -0
- claude_mpm/ui/__init__.py +1 -0
- claude_mpm/ui/rich_terminal_ui.py +295 -0
- claude_mpm/ui/terminal_ui.py +328 -0
- claude_mpm/utils/__init__.py +16 -0
- claude_mpm/utils/config_manager.py +468 -0
- claude_mpm/utils/import_migration_example.py +80 -0
- claude_mpm/utils/imports.py +182 -0
- claude_mpm/utils/path_operations.py +357 -0
- claude_mpm/utils/paths.py +289 -0
- claude_mpm-0.3.0.dist-info/METADATA +290 -0
- claude_mpm-0.3.0.dist-info/RECORD +159 -0
- claude_mpm-0.3.0.dist-info/WHEEL +5 -0
- claude_mpm-0.3.0.dist-info/entry_points.txt +4 -0
- claude_mpm-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
"""Framework loader for Claude MPM."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import logging
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional, Dict, Any
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
|
|
9
|
+
from ..utils.imports import safe_import
|
|
10
|
+
|
|
11
|
+
# Import with fallback support - using absolute imports as primary since we're at module level
|
|
12
|
+
get_logger = safe_import('claude_mpm.core.logger', 'core.logger', ['get_logger'])
|
|
13
|
+
AgentRegistryAdapter = safe_import('claude_mpm.core.agent_registry', 'core.agent_registry', ['AgentRegistryAdapter'])
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FrameworkLoader:
|
|
17
|
+
"""
|
|
18
|
+
Load and prepare framework instructions for injection.
|
|
19
|
+
|
|
20
|
+
This component handles:
|
|
21
|
+
1. Finding the framework (claude-multiagent-pm)
|
|
22
|
+
2. Loading INSTRUCTIONS.md instructions
|
|
23
|
+
3. Preparing agent definitions
|
|
24
|
+
4. Formatting for injection
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, framework_path: Optional[Path] = None, agents_dir: Optional[Path] = None):
|
|
28
|
+
"""
|
|
29
|
+
Initialize framework loader.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
framework_path: Explicit path to framework (auto-detected if None)
|
|
33
|
+
agents_dir: Custom agents directory (overrides framework agents)
|
|
34
|
+
"""
|
|
35
|
+
self.logger = get_logger("framework_loader")
|
|
36
|
+
self.framework_path = framework_path or self._detect_framework_path()
|
|
37
|
+
self.agents_dir = agents_dir
|
|
38
|
+
self.framework_version = None
|
|
39
|
+
self.framework_last_modified = None
|
|
40
|
+
self.framework_content = self._load_framework_content()
|
|
41
|
+
|
|
42
|
+
# Initialize agent registry
|
|
43
|
+
self.agent_registry = AgentRegistryAdapter(self.framework_path)
|
|
44
|
+
|
|
45
|
+
def _detect_framework_path(self) -> Optional[Path]:
|
|
46
|
+
"""Auto-detect claude-mpm framework."""
|
|
47
|
+
# First check if we're in claude-mpm project
|
|
48
|
+
current_file = Path(__file__)
|
|
49
|
+
if "claude-mpm" in str(current_file):
|
|
50
|
+
# We're running from claude-mpm, use its agents
|
|
51
|
+
for parent in current_file.parents:
|
|
52
|
+
if parent.name == "claude-mpm":
|
|
53
|
+
if (parent / "src" / "claude_mpm" / "agents").exists():
|
|
54
|
+
self.logger.info(f"Using claude-mpm at: {parent}")
|
|
55
|
+
return parent
|
|
56
|
+
break
|
|
57
|
+
|
|
58
|
+
# Otherwise check common locations for claude-mpm
|
|
59
|
+
candidates = [
|
|
60
|
+
# Development location
|
|
61
|
+
Path.home() / "Projects" / "claude-mpm",
|
|
62
|
+
# Current directory
|
|
63
|
+
Path.cwd() / "claude-mpm",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
for candidate in candidates:
|
|
67
|
+
if candidate and candidate.exists():
|
|
68
|
+
# Check for claude-mpm agents directory
|
|
69
|
+
if (candidate / "src" / "claude_mpm" / "agents").exists():
|
|
70
|
+
self.logger.info(f"Found claude-mpm at: {candidate}")
|
|
71
|
+
return candidate
|
|
72
|
+
|
|
73
|
+
self.logger.warning("Framework not found, will use minimal instructions")
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
def _get_npm_global_path(self) -> Optional[Path]:
|
|
77
|
+
"""Get npm global installation path."""
|
|
78
|
+
try:
|
|
79
|
+
import subprocess
|
|
80
|
+
result = subprocess.run(
|
|
81
|
+
["npm", "root", "-g"],
|
|
82
|
+
capture_output=True,
|
|
83
|
+
text=True,
|
|
84
|
+
timeout=5
|
|
85
|
+
)
|
|
86
|
+
if result.returncode == 0:
|
|
87
|
+
npm_root = Path(result.stdout.strip())
|
|
88
|
+
return npm_root / "@bobmatnyc" / "claude-multiagent-pm"
|
|
89
|
+
except:
|
|
90
|
+
pass
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
def _discover_framework_paths(self) -> tuple[Optional[Path], Optional[Path], Optional[Path]]:
|
|
94
|
+
"""
|
|
95
|
+
Discover agent directories based on priority.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Tuple of (agents_dir, templates_dir, main_dir)
|
|
99
|
+
"""
|
|
100
|
+
agents_dir = None
|
|
101
|
+
templates_dir = None
|
|
102
|
+
main_dir = None
|
|
103
|
+
|
|
104
|
+
if self.agents_dir and self.agents_dir.exists():
|
|
105
|
+
agents_dir = self.agents_dir
|
|
106
|
+
self.logger.info(f"Using custom agents directory: {agents_dir}")
|
|
107
|
+
elif self.framework_path:
|
|
108
|
+
# Prioritize templates directory over main agents directory
|
|
109
|
+
templates_dir = self.framework_path / "src" / "claude_mpm" / "agents" / "templates"
|
|
110
|
+
main_dir = self.framework_path / "src" / "claude_mpm" / "agents"
|
|
111
|
+
|
|
112
|
+
if templates_dir.exists() and any(templates_dir.glob("*.md")):
|
|
113
|
+
agents_dir = templates_dir
|
|
114
|
+
self.logger.info(f"Using agents from templates directory: {agents_dir}")
|
|
115
|
+
elif main_dir.exists() and any(main_dir.glob("*.md")):
|
|
116
|
+
agents_dir = main_dir
|
|
117
|
+
self.logger.info(f"Using agents from main directory: {agents_dir}")
|
|
118
|
+
|
|
119
|
+
return agents_dir, templates_dir, main_dir
|
|
120
|
+
|
|
121
|
+
def _try_load_file(self, file_path: Path, file_type: str) -> Optional[str]:
|
|
122
|
+
"""
|
|
123
|
+
Try to load a file with error handling.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
file_path: Path to the file to load
|
|
127
|
+
file_type: Description of file type for logging
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
File content if successful, None otherwise
|
|
131
|
+
"""
|
|
132
|
+
try:
|
|
133
|
+
content = file_path.read_text()
|
|
134
|
+
if hasattr(self.logger, 'level') and self.logger.level <= logging.INFO:
|
|
135
|
+
self.logger.info(f"Loaded {file_type} from: {file_path}")
|
|
136
|
+
|
|
137
|
+
# Extract metadata if present
|
|
138
|
+
import re
|
|
139
|
+
version_match = re.search(r'<!-- FRAMEWORK_VERSION: (\d+) -->', content)
|
|
140
|
+
if version_match:
|
|
141
|
+
version = version_match.group(1) # Keep as string to preserve leading zeros
|
|
142
|
+
self.logger.info(f"Framework version: {version}")
|
|
143
|
+
# Store framework version if this is the main INSTRUCTIONS.md
|
|
144
|
+
if 'INSTRUCTIONS.md' in str(file_path):
|
|
145
|
+
self.framework_version = version
|
|
146
|
+
|
|
147
|
+
# Extract modification timestamp
|
|
148
|
+
timestamp_match = re.search(r'<!-- LAST_MODIFIED: ([^>]+) -->', content)
|
|
149
|
+
if timestamp_match:
|
|
150
|
+
timestamp = timestamp_match.group(1).strip()
|
|
151
|
+
self.logger.info(f"Last modified: {timestamp}")
|
|
152
|
+
# Store timestamp if this is the main INSTRUCTIONS.md
|
|
153
|
+
if 'INSTRUCTIONS.md' in str(file_path):
|
|
154
|
+
self.framework_last_modified = timestamp
|
|
155
|
+
|
|
156
|
+
return content
|
|
157
|
+
except Exception as e:
|
|
158
|
+
if hasattr(self.logger, 'level') and self.logger.level <= logging.ERROR:
|
|
159
|
+
self.logger.error(f"Failed to load {file_type}: {e}")
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
def _load_instructions_file(self, content: Dict[str, Any]) -> None:
|
|
163
|
+
"""
|
|
164
|
+
Load INSTRUCTIONS.md or legacy CLAUDE.md from working directory.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
content: Dictionary to update with loaded instructions
|
|
168
|
+
"""
|
|
169
|
+
working_instructions = Path.cwd() / "INSTRUCTIONS.md"
|
|
170
|
+
working_claude = Path.cwd() / "CLAUDE.md" # Legacy support
|
|
171
|
+
|
|
172
|
+
if working_instructions.exists():
|
|
173
|
+
loaded_content = self._try_load_file(working_instructions, "working directory INSTRUCTIONS.md")
|
|
174
|
+
if loaded_content:
|
|
175
|
+
content["working_claude_md"] = loaded_content
|
|
176
|
+
elif working_claude.exists():
|
|
177
|
+
# Legacy support for CLAUDE.md
|
|
178
|
+
loaded_content = self._try_load_file(working_claude, "working directory CLAUDE.md (legacy)")
|
|
179
|
+
if loaded_content:
|
|
180
|
+
content["working_claude_md"] = loaded_content
|
|
181
|
+
|
|
182
|
+
def _load_single_agent(self, agent_file: Path) -> tuple[Optional[str], Optional[str]]:
|
|
183
|
+
"""
|
|
184
|
+
Load a single agent file.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
agent_file: Path to the agent file
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Tuple of (agent_name, agent_content) or (None, None) on failure
|
|
191
|
+
"""
|
|
192
|
+
try:
|
|
193
|
+
agent_name = agent_file.stem
|
|
194
|
+
# Skip README files
|
|
195
|
+
if agent_name.upper() == "README":
|
|
196
|
+
return None, None
|
|
197
|
+
content = agent_file.read_text()
|
|
198
|
+
self.logger.debug(f"Loaded agent: {agent_name}")
|
|
199
|
+
return agent_name, content
|
|
200
|
+
except Exception as e:
|
|
201
|
+
self.logger.error(f"Failed to load agent {agent_file}: {e}")
|
|
202
|
+
return None, None
|
|
203
|
+
|
|
204
|
+
def _load_base_agent_fallback(self, content: Dict[str, Any], main_dir: Optional[Path]) -> None:
|
|
205
|
+
"""
|
|
206
|
+
Load base_agent.md from main directory as fallback.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
content: Dictionary to update with base agent
|
|
210
|
+
main_dir: Main agents directory path
|
|
211
|
+
"""
|
|
212
|
+
if main_dir and main_dir.exists() and "base_agent" not in content["agents"]:
|
|
213
|
+
base_agent_file = main_dir / "base_agent.md"
|
|
214
|
+
if base_agent_file.exists():
|
|
215
|
+
agent_name, agent_content = self._load_single_agent(base_agent_file)
|
|
216
|
+
if agent_name and agent_content:
|
|
217
|
+
content["agents"][agent_name] = agent_content
|
|
218
|
+
|
|
219
|
+
def _load_agents_directory(self, content: Dict[str, Any], agents_dir: Optional[Path],
|
|
220
|
+
templates_dir: Optional[Path], main_dir: Optional[Path]) -> None:
|
|
221
|
+
"""
|
|
222
|
+
Load agent definitions from the appropriate directory.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
content: Dictionary to update with loaded agents
|
|
226
|
+
agents_dir: Primary agents directory to load from
|
|
227
|
+
templates_dir: Templates directory path
|
|
228
|
+
main_dir: Main agents directory path
|
|
229
|
+
"""
|
|
230
|
+
if not agents_dir or not agents_dir.exists():
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
content["loaded"] = True
|
|
234
|
+
|
|
235
|
+
# Load all agent files
|
|
236
|
+
for agent_file in agents_dir.glob("*.md"):
|
|
237
|
+
agent_name, agent_content = self._load_single_agent(agent_file)
|
|
238
|
+
if agent_name and agent_content:
|
|
239
|
+
content["agents"][agent_name] = agent_content
|
|
240
|
+
|
|
241
|
+
# If we used templates dir, also check main dir for base_agent.md
|
|
242
|
+
if agents_dir == templates_dir:
|
|
243
|
+
self._load_base_agent_fallback(content, main_dir)
|
|
244
|
+
|
|
245
|
+
def _load_framework_content(self) -> Dict[str, Any]:
|
|
246
|
+
"""Load framework content."""
|
|
247
|
+
content = {
|
|
248
|
+
"claude_md": "",
|
|
249
|
+
"agents": {},
|
|
250
|
+
"version": "unknown",
|
|
251
|
+
"loaded": False,
|
|
252
|
+
"working_claude_md": "",
|
|
253
|
+
"framework_instructions": ""
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
# Load instructions file from working directory
|
|
257
|
+
self._load_instructions_file(content)
|
|
258
|
+
|
|
259
|
+
if not self.framework_path:
|
|
260
|
+
return content
|
|
261
|
+
|
|
262
|
+
# Load framework's INSTRUCTIONS.md
|
|
263
|
+
framework_instructions_path = self.framework_path / "src" / "claude_mpm" / "agents" / "INSTRUCTIONS.md"
|
|
264
|
+
if framework_instructions_path.exists():
|
|
265
|
+
loaded_content = self._try_load_file(framework_instructions_path, "framework INSTRUCTIONS.md")
|
|
266
|
+
if loaded_content:
|
|
267
|
+
content["framework_instructions"] = loaded_content
|
|
268
|
+
content["loaded"] = True
|
|
269
|
+
# Add framework version to content
|
|
270
|
+
if self.framework_version:
|
|
271
|
+
content["instructions_version"] = self.framework_version
|
|
272
|
+
content["version"] = self.framework_version # Update main version key
|
|
273
|
+
# Add modification timestamp to content
|
|
274
|
+
if self.framework_last_modified:
|
|
275
|
+
content["instructions_last_modified"] = self.framework_last_modified
|
|
276
|
+
|
|
277
|
+
# Discover agent directories
|
|
278
|
+
agents_dir, templates_dir, main_dir = self._discover_framework_paths()
|
|
279
|
+
|
|
280
|
+
# Load agents from discovered directory
|
|
281
|
+
self._load_agents_directory(content, agents_dir, templates_dir, main_dir)
|
|
282
|
+
|
|
283
|
+
return content
|
|
284
|
+
|
|
285
|
+
def get_framework_instructions(self) -> str:
|
|
286
|
+
"""
|
|
287
|
+
Get formatted framework instructions for injection.
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
Complete framework instructions ready for injection
|
|
291
|
+
"""
|
|
292
|
+
if self.framework_content["loaded"] or self.framework_content["working_claude_md"]:
|
|
293
|
+
# Build framework from components
|
|
294
|
+
return self._format_full_framework()
|
|
295
|
+
else:
|
|
296
|
+
# Use minimal fallback
|
|
297
|
+
return self._format_minimal_framework()
|
|
298
|
+
|
|
299
|
+
def _format_full_framework(self) -> str:
|
|
300
|
+
"""Format full framework instructions."""
|
|
301
|
+
from datetime import datetime
|
|
302
|
+
|
|
303
|
+
# If we have the full framework INSTRUCTIONS.md, use it
|
|
304
|
+
if self.framework_content.get("framework_instructions"):
|
|
305
|
+
instructions = self.framework_content["framework_instructions"]
|
|
306
|
+
|
|
307
|
+
# Add working directory instructions if they exist
|
|
308
|
+
if self.framework_content["working_claude_md"]:
|
|
309
|
+
instructions += f"\n\n## Working Directory Instructions\n{self.framework_content['working_claude_md']}\n"
|
|
310
|
+
|
|
311
|
+
return instructions
|
|
312
|
+
|
|
313
|
+
# Otherwise fall back to generating framework
|
|
314
|
+
instructions = f"""
|
|
315
|
+
<!-- Framework injected by Claude MPM -->
|
|
316
|
+
<!-- Version: {self.framework_content['version']} -->
|
|
317
|
+
<!-- Timestamp: {datetime.now().isoformat()} -->
|
|
318
|
+
|
|
319
|
+
# Claude MPM Framework Instructions
|
|
320
|
+
|
|
321
|
+
You are operating within the Claude Multi-Agent Project Manager (MPM) framework.
|
|
322
|
+
|
|
323
|
+
## Core Role
|
|
324
|
+
You are a multi-agent orchestrator. Your primary responsibilities are:
|
|
325
|
+
- Delegate all implementation work to specialized agents via Task Tool
|
|
326
|
+
- Coordinate multi-agent workflows and cross-agent collaboration
|
|
327
|
+
- Extract and track TODO/BUG/FEATURE items for ticket creation
|
|
328
|
+
- Maintain project visibility and strategic oversight
|
|
329
|
+
- NEVER perform direct implementation work yourself
|
|
330
|
+
|
|
331
|
+
"""
|
|
332
|
+
|
|
333
|
+
# Add working directory INSTRUCTIONS.md (or CLAUDE.md) if exists
|
|
334
|
+
if self.framework_content["working_claude_md"]:
|
|
335
|
+
instructions += f"""
|
|
336
|
+
## Working Directory Instructions
|
|
337
|
+
{self.framework_content["working_claude_md"]}
|
|
338
|
+
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
# Add agent definitions
|
|
342
|
+
if self.framework_content["agents"]:
|
|
343
|
+
instructions += "## Available Agents\n\n"
|
|
344
|
+
instructions += "You have the following specialized agents available for delegation:\n\n"
|
|
345
|
+
|
|
346
|
+
# List agents with brief descriptions
|
|
347
|
+
agent_list = []
|
|
348
|
+
for agent_name in sorted(self.framework_content["agents"].keys()):
|
|
349
|
+
clean_name = agent_name.replace('-', ' ').replace('_', ' ').title()
|
|
350
|
+
if 'engineer' in agent_name.lower():
|
|
351
|
+
agent_list.append(f"- **Engineer Agent**: Code implementation and development")
|
|
352
|
+
elif 'qa' in agent_name.lower():
|
|
353
|
+
agent_list.append(f"- **QA Agent**: Testing and quality assurance")
|
|
354
|
+
elif 'documentation' in agent_name.lower():
|
|
355
|
+
agent_list.append(f"- **Documentation Agent**: Documentation creation and maintenance")
|
|
356
|
+
elif 'research' in agent_name.lower():
|
|
357
|
+
agent_list.append(f"- **Research Agent**: Investigation and analysis")
|
|
358
|
+
elif 'security' in agent_name.lower():
|
|
359
|
+
agent_list.append(f"- **Security Agent**: Security analysis and protection")
|
|
360
|
+
elif 'version' in agent_name.lower():
|
|
361
|
+
agent_list.append(f"- **Version Control Agent**: Git operations and version management")
|
|
362
|
+
elif 'ops' in agent_name.lower():
|
|
363
|
+
agent_list.append(f"- **Ops Agent**: Deployment and operations")
|
|
364
|
+
elif 'data' in agent_name.lower():
|
|
365
|
+
agent_list.append(f"- **Data Engineer Agent**: Data management and AI API integration")
|
|
366
|
+
else:
|
|
367
|
+
agent_list.append(f"- **{clean_name}**: Available for specialized tasks")
|
|
368
|
+
|
|
369
|
+
instructions += "\n".join(agent_list) + "\n\n"
|
|
370
|
+
|
|
371
|
+
# Add full agent details
|
|
372
|
+
instructions += "### Agent Details\n\n"
|
|
373
|
+
for agent_name, agent_content in sorted(self.framework_content["agents"].items()):
|
|
374
|
+
instructions += f"#### {agent_name.replace('-', ' ').title()}\n"
|
|
375
|
+
instructions += agent_content + "\n\n"
|
|
376
|
+
|
|
377
|
+
# Add orchestration principles
|
|
378
|
+
instructions += """
|
|
379
|
+
## Orchestration Principles
|
|
380
|
+
1. **Always Delegate**: Never perform direct work - use Task Tool for all implementation
|
|
381
|
+
2. **Comprehensive Context**: Provide rich, filtered context to each agent
|
|
382
|
+
3. **Track Everything**: Extract all TODO/BUG/FEATURE items systematically
|
|
383
|
+
4. **Cross-Agent Coordination**: Orchestrate workflows spanning multiple agents
|
|
384
|
+
5. **Results Integration**: Actively receive and integrate agent results
|
|
385
|
+
|
|
386
|
+
## Task Tool Format
|
|
387
|
+
```
|
|
388
|
+
**[Agent Name]**: [Clear task description with deliverables]
|
|
389
|
+
|
|
390
|
+
TEMPORAL CONTEXT: Today is [date]. Apply date awareness to [specific considerations].
|
|
391
|
+
|
|
392
|
+
**Task**: [Detailed task breakdown]
|
|
393
|
+
1. [Specific action item 1]
|
|
394
|
+
2. [Specific action item 2]
|
|
395
|
+
3. [Specific action item 3]
|
|
396
|
+
|
|
397
|
+
**Context**: [Comprehensive filtered context for this agent]
|
|
398
|
+
**Authority**: [Agent's decision-making scope]
|
|
399
|
+
**Expected Results**: [Specific deliverables needed]
|
|
400
|
+
**Integration**: [How results integrate with other work]
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
## Ticket Extraction Patterns
|
|
404
|
+
Extract tickets from these patterns:
|
|
405
|
+
- TODO: [description] → TODO ticket
|
|
406
|
+
- BUG: [description] → BUG ticket
|
|
407
|
+
- FEATURE: [description] → FEATURE ticket
|
|
408
|
+
- ISSUE: [description] → ISSUE ticket
|
|
409
|
+
- FIXME: [description] → BUG ticket
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
"""
|
|
413
|
+
|
|
414
|
+
return instructions
|
|
415
|
+
|
|
416
|
+
def _format_minimal_framework(self) -> str:
|
|
417
|
+
"""Format minimal framework instructions when full framework not available."""
|
|
418
|
+
return """
|
|
419
|
+
# Claude PM Framework Instructions
|
|
420
|
+
|
|
421
|
+
You are operating within a Claude PM Framework deployment.
|
|
422
|
+
|
|
423
|
+
## Role
|
|
424
|
+
You are a multi-agent orchestrator. Your primary responsibilities:
|
|
425
|
+
- Delegate tasks to specialized agents via Task Tool
|
|
426
|
+
- Coordinate multi-agent workflows
|
|
427
|
+
- Extract TODO/BUG/FEATURE items for ticket creation
|
|
428
|
+
- NEVER perform direct implementation work
|
|
429
|
+
|
|
430
|
+
## Core Agents
|
|
431
|
+
- Documentation Agent - Documentation tasks
|
|
432
|
+
- Engineer Agent - Code implementation
|
|
433
|
+
- QA Agent - Testing and validation
|
|
434
|
+
- Research Agent - Investigation and analysis
|
|
435
|
+
- Version Control Agent - Git operations
|
|
436
|
+
|
|
437
|
+
## Important Rules
|
|
438
|
+
1. Always delegate work via Task Tool
|
|
439
|
+
2. Provide comprehensive context to agents
|
|
440
|
+
3. Track all TODO/BUG/FEATURE items
|
|
441
|
+
4. Maintain project visibility
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
"""
|
|
445
|
+
|
|
446
|
+
def get_agent_list(self) -> list:
|
|
447
|
+
"""Get list of available agents."""
|
|
448
|
+
# First try agent registry
|
|
449
|
+
if self.agent_registry:
|
|
450
|
+
agents = self.agent_registry.list_agents()
|
|
451
|
+
if agents:
|
|
452
|
+
return list(agents.keys())
|
|
453
|
+
|
|
454
|
+
# Fallback to loaded content
|
|
455
|
+
return list(self.framework_content["agents"].keys())
|
|
456
|
+
|
|
457
|
+
def get_agent_definition(self, agent_name: str) -> Optional[str]:
|
|
458
|
+
"""Get specific agent definition."""
|
|
459
|
+
# First try agent registry
|
|
460
|
+
if self.agent_registry:
|
|
461
|
+
definition = self.agent_registry.get_agent_definition(agent_name)
|
|
462
|
+
if definition:
|
|
463
|
+
return definition
|
|
464
|
+
|
|
465
|
+
# Fallback to loaded content
|
|
466
|
+
return self.framework_content["agents"].get(agent_name)
|
|
467
|
+
|
|
468
|
+
def get_agent_hierarchy(self) -> Dict[str, list]:
|
|
469
|
+
"""Get agent hierarchy from registry."""
|
|
470
|
+
if self.agent_registry:
|
|
471
|
+
return self.agent_registry.get_agent_hierarchy()
|
|
472
|
+
return {'project': [], 'user': [], 'system': []}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Injectable Service Base Class for Claude MPM.
|
|
3
|
+
|
|
4
|
+
Extends BaseService with enhanced dependency injection support,
|
|
5
|
+
making services easier to test and configure.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC
|
|
9
|
+
from typing import Any, Dict, Optional, Type, TypeVar, Union
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from .base_service import BaseService
|
|
13
|
+
from .container import DIContainer
|
|
14
|
+
from .logger import get_logger
|
|
15
|
+
|
|
16
|
+
logger = get_logger(__name__)
|
|
17
|
+
|
|
18
|
+
T = TypeVar('T')
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class InjectableService(BaseService, ABC):
|
|
22
|
+
"""
|
|
23
|
+
Enhanced base service with full dependency injection support.
|
|
24
|
+
|
|
25
|
+
Features:
|
|
26
|
+
- Automatic dependency resolution from container
|
|
27
|
+
- Property injection support
|
|
28
|
+
- Service locator pattern (optional)
|
|
29
|
+
- Easy testing with mock dependencies
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
name: str,
|
|
35
|
+
config: Optional[Dict[str, Any]] = None,
|
|
36
|
+
config_path: Optional[Path] = None,
|
|
37
|
+
enable_enhanced_features: bool = True,
|
|
38
|
+
container: Optional[DIContainer] = None,
|
|
39
|
+
**injected_deps
|
|
40
|
+
):
|
|
41
|
+
"""
|
|
42
|
+
Initialize injectable service.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
name: Service name
|
|
46
|
+
config: Configuration dictionary
|
|
47
|
+
config_path: Path to configuration file
|
|
48
|
+
enable_enhanced_features: Enable enhanced features
|
|
49
|
+
container: DI container for dependency resolution
|
|
50
|
+
**injected_deps: Explicitly injected dependencies
|
|
51
|
+
"""
|
|
52
|
+
# Store container before calling parent init
|
|
53
|
+
self._di_container = container
|
|
54
|
+
self._injected_deps = injected_deps
|
|
55
|
+
|
|
56
|
+
super().__init__(
|
|
57
|
+
name=name,
|
|
58
|
+
config=config,
|
|
59
|
+
config_path=config_path,
|
|
60
|
+
enable_enhanced_features=enable_enhanced_features,
|
|
61
|
+
container=container
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Inject dependencies after base initialization
|
|
65
|
+
self._inject_dependencies()
|
|
66
|
+
|
|
67
|
+
def _inject_dependencies(self) -> None:
|
|
68
|
+
"""Inject dependencies based on class annotations."""
|
|
69
|
+
# Get class annotations for dependency injection
|
|
70
|
+
annotations = getattr(self.__class__, '__annotations__', {})
|
|
71
|
+
|
|
72
|
+
for attr_name, attr_type in annotations.items():
|
|
73
|
+
# Skip if already set or is a private attribute
|
|
74
|
+
if hasattr(self, attr_name) or attr_name.startswith('_'):
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
# Check if explicitly injected
|
|
78
|
+
if attr_name in self._injected_deps:
|
|
79
|
+
setattr(self, attr_name, self._injected_deps[attr_name])
|
|
80
|
+
logger.debug(f"Injected {attr_name} from explicit dependencies")
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
# Try to resolve from container
|
|
84
|
+
if self._di_container:
|
|
85
|
+
try:
|
|
86
|
+
# Handle Optional types
|
|
87
|
+
if hasattr(attr_type, '__origin__'):
|
|
88
|
+
if attr_type.__origin__ is Union:
|
|
89
|
+
# Get the non-None type from Optional
|
|
90
|
+
args = attr_type.__args__
|
|
91
|
+
actual_type = next((arg for arg in args if arg is not type(None)), None)
|
|
92
|
+
if actual_type:
|
|
93
|
+
service = self._di_container.resolve_optional(actual_type)
|
|
94
|
+
if service:
|
|
95
|
+
setattr(self, attr_name, service)
|
|
96
|
+
logger.debug(f"Injected optional {attr_name} from container")
|
|
97
|
+
else:
|
|
98
|
+
# Direct type resolution
|
|
99
|
+
service = self._di_container.resolve(attr_type)
|
|
100
|
+
setattr(self, attr_name, service)
|
|
101
|
+
logger.debug(f"Injected {attr_name} from container")
|
|
102
|
+
except Exception as e:
|
|
103
|
+
logger.warning(f"Could not inject {attr_name}: {e}")
|
|
104
|
+
|
|
105
|
+
def get_dependency(self, service_type: Type[T]) -> T:
|
|
106
|
+
"""
|
|
107
|
+
Get a dependency from the container.
|
|
108
|
+
|
|
109
|
+
Service locator pattern - use sparingly, prefer constructor injection.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
service_type: Type to resolve
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Resolved service instance
|
|
116
|
+
"""
|
|
117
|
+
if not self._di_container:
|
|
118
|
+
raise RuntimeError("No DI container available")
|
|
119
|
+
|
|
120
|
+
return self._di_container.resolve(service_type)
|
|
121
|
+
|
|
122
|
+
def get_optional_dependency(
|
|
123
|
+
self,
|
|
124
|
+
service_type: Type[T],
|
|
125
|
+
default: Optional[T] = None
|
|
126
|
+
) -> Optional[T]:
|
|
127
|
+
"""
|
|
128
|
+
Get an optional dependency from the container.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
service_type: Type to resolve
|
|
132
|
+
default: Default value if not found
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Resolved service or default
|
|
136
|
+
"""
|
|
137
|
+
if not self._di_container:
|
|
138
|
+
return default
|
|
139
|
+
|
|
140
|
+
return self._di_container.resolve_optional(service_type, default)
|
|
141
|
+
|
|
142
|
+
def has_dependency(self, service_type: Type) -> bool:
|
|
143
|
+
"""Check if a dependency is available in the container."""
|
|
144
|
+
if not self._di_container:
|
|
145
|
+
return False
|
|
146
|
+
|
|
147
|
+
return self._di_container.is_registered(service_type)
|
|
148
|
+
|
|
149
|
+
def with_dependencies(self, **deps) -> 'InjectableService':
|
|
150
|
+
"""
|
|
151
|
+
Create a new instance with specific dependencies.
|
|
152
|
+
|
|
153
|
+
Useful for testing with mocks.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
**deps: Dependencies to inject
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
New service instance with injected dependencies
|
|
160
|
+
"""
|
|
161
|
+
# Merge with existing config
|
|
162
|
+
config = self.config._config if hasattr(self.config, '_config') else {}
|
|
163
|
+
|
|
164
|
+
# Create new instance with dependencies
|
|
165
|
+
return self.__class__(
|
|
166
|
+
name=self.name,
|
|
167
|
+
config=config,
|
|
168
|
+
enable_enhanced_features=self._enable_enhanced,
|
|
169
|
+
container=self._di_container,
|
|
170
|
+
**deps
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
async def _initialize_dependencies(self) -> None:
|
|
174
|
+
"""Initialize injected dependencies that need async setup."""
|
|
175
|
+
# Initialize any dependencies that need async initialization
|
|
176
|
+
for attr_name in dir(self):
|
|
177
|
+
if attr_name.startswith('_'):
|
|
178
|
+
continue
|
|
179
|
+
|
|
180
|
+
attr = getattr(self, attr_name, None)
|
|
181
|
+
if attr and hasattr(attr, 'start') and hasattr(attr, 'running'):
|
|
182
|
+
# It's a service that needs to be started
|
|
183
|
+
if not attr.running:
|
|
184
|
+
try:
|
|
185
|
+
await attr.start()
|
|
186
|
+
logger.debug(f"Started dependency {attr_name}")
|
|
187
|
+
except Exception as e:
|
|
188
|
+
logger.warning(f"Failed to start dependency {attr_name}: {e}")
|
|
189
|
+
|
|
190
|
+
def __repr__(self) -> str:
|
|
191
|
+
"""Enhanced string representation showing dependencies."""
|
|
192
|
+
base_repr = super().__repr__()
|
|
193
|
+
|
|
194
|
+
# Add dependency information
|
|
195
|
+
deps = []
|
|
196
|
+
annotations = getattr(self.__class__, '__annotations__', {})
|
|
197
|
+
|
|
198
|
+
for attr_name, attr_type in annotations.items():
|
|
199
|
+
if hasattr(self, attr_name) and not attr_name.startswith('_'):
|
|
200
|
+
deps.append(f"{attr_name}={getattr(self, attr_name).__class__.__name__}")
|
|
201
|
+
|
|
202
|
+
if deps:
|
|
203
|
+
deps_str = ", ".join(deps)
|
|
204
|
+
return base_repr.replace(')>', f', deps=[{deps_str}])>')
|
|
205
|
+
|
|
206
|
+
return base_repr
|