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.

Files changed (159) hide show
  1. claude_mpm/__init__.py +17 -0
  2. claude_mpm/__main__.py +14 -0
  3. claude_mpm/_version.py +32 -0
  4. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +88 -0
  5. claude_mpm/agents/INSTRUCTIONS.md +375 -0
  6. claude_mpm/agents/__init__.py +118 -0
  7. claude_mpm/agents/agent_loader.py +621 -0
  8. claude_mpm/agents/agent_loader_integration.py +229 -0
  9. claude_mpm/agents/agents_metadata.py +204 -0
  10. claude_mpm/agents/base_agent.json +27 -0
  11. claude_mpm/agents/base_agent_loader.py +519 -0
  12. claude_mpm/agents/schema/agent_schema.json +160 -0
  13. claude_mpm/agents/system_agent_config.py +587 -0
  14. claude_mpm/agents/templates/__init__.py +101 -0
  15. claude_mpm/agents/templates/data_engineer_agent.json +46 -0
  16. claude_mpm/agents/templates/documentation_agent.json +45 -0
  17. claude_mpm/agents/templates/engineer_agent.json +49 -0
  18. claude_mpm/agents/templates/ops_agent.json +46 -0
  19. claude_mpm/agents/templates/qa_agent.json +45 -0
  20. claude_mpm/agents/templates/research_agent.json +49 -0
  21. claude_mpm/agents/templates/security_agent.json +46 -0
  22. claude_mpm/agents/templates/update-optimized-specialized-agents.json +374 -0
  23. claude_mpm/agents/templates/version_control_agent.json +46 -0
  24. claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +6 -0
  25. claude_mpm/cli.py +655 -0
  26. claude_mpm/cli_main.py +13 -0
  27. claude_mpm/cli_module/__init__.py +15 -0
  28. claude_mpm/cli_module/args.py +222 -0
  29. claude_mpm/cli_module/commands.py +203 -0
  30. claude_mpm/cli_module/migration_example.py +183 -0
  31. claude_mpm/cli_module/refactoring_guide.md +253 -0
  32. claude_mpm/cli_old/__init__.py +1 -0
  33. claude_mpm/cli_old/ticket_cli.py +102 -0
  34. claude_mpm/config/__init__.py +5 -0
  35. claude_mpm/config/hook_config.py +42 -0
  36. claude_mpm/constants.py +150 -0
  37. claude_mpm/core/__init__.py +45 -0
  38. claude_mpm/core/agent_name_normalizer.py +248 -0
  39. claude_mpm/core/agent_registry.py +627 -0
  40. claude_mpm/core/agent_registry.py.bak +312 -0
  41. claude_mpm/core/agent_session_manager.py +273 -0
  42. claude_mpm/core/base_service.py +747 -0
  43. claude_mpm/core/base_service.py.bak +406 -0
  44. claude_mpm/core/config.py +334 -0
  45. claude_mpm/core/config_aliases.py +292 -0
  46. claude_mpm/core/container.py +347 -0
  47. claude_mpm/core/factories.py +281 -0
  48. claude_mpm/core/framework_loader.py +472 -0
  49. claude_mpm/core/injectable_service.py +206 -0
  50. claude_mpm/core/interfaces.py +539 -0
  51. claude_mpm/core/logger.py +468 -0
  52. claude_mpm/core/minimal_framework_loader.py +107 -0
  53. claude_mpm/core/mixins.py +150 -0
  54. claude_mpm/core/service_registry.py +299 -0
  55. claude_mpm/core/session_manager.py +190 -0
  56. claude_mpm/core/simple_runner.py +511 -0
  57. claude_mpm/core/tool_access_control.py +173 -0
  58. claude_mpm/hooks/README.md +243 -0
  59. claude_mpm/hooks/__init__.py +5 -0
  60. claude_mpm/hooks/base_hook.py +154 -0
  61. claude_mpm/hooks/builtin/__init__.py +1 -0
  62. claude_mpm/hooks/builtin/logging_hook_example.py +165 -0
  63. claude_mpm/hooks/builtin/post_delegation_hook_example.py +124 -0
  64. claude_mpm/hooks/builtin/pre_delegation_hook_example.py +125 -0
  65. claude_mpm/hooks/builtin/submit_hook_example.py +100 -0
  66. claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +237 -0
  67. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +239 -0
  68. claude_mpm/hooks/builtin/workflow_start_hook.py +181 -0
  69. claude_mpm/hooks/hook_client.py +264 -0
  70. claude_mpm/hooks/hook_runner.py +370 -0
  71. claude_mpm/hooks/json_rpc_executor.py +259 -0
  72. claude_mpm/hooks/json_rpc_hook_client.py +319 -0
  73. claude_mpm/hooks/tool_call_interceptor.py +204 -0
  74. claude_mpm/init.py +246 -0
  75. claude_mpm/orchestration/SUBPROCESS_DESIGN.md +66 -0
  76. claude_mpm/orchestration/__init__.py +6 -0
  77. claude_mpm/orchestration/archive/direct_orchestrator.py +195 -0
  78. claude_mpm/orchestration/archive/factory.py +215 -0
  79. claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +188 -0
  80. claude_mpm/orchestration/archive/hook_integration_example.py +178 -0
  81. claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +826 -0
  82. claude_mpm/orchestration/archive/orchestrator.py +501 -0
  83. claude_mpm/orchestration/archive/pexpect_orchestrator.py +252 -0
  84. claude_mpm/orchestration/archive/pty_orchestrator.py +270 -0
  85. claude_mpm/orchestration/archive/simple_orchestrator.py +82 -0
  86. claude_mpm/orchestration/archive/subprocess_orchestrator.py +801 -0
  87. claude_mpm/orchestration/archive/system_prompt_orchestrator.py +278 -0
  88. claude_mpm/orchestration/archive/wrapper_orchestrator.py +187 -0
  89. claude_mpm/scripts/__init__.py +1 -0
  90. claude_mpm/scripts/ticket.py +269 -0
  91. claude_mpm/services/__init__.py +10 -0
  92. claude_mpm/services/agent_deployment.py +955 -0
  93. claude_mpm/services/agent_lifecycle_manager.py +948 -0
  94. claude_mpm/services/agent_management_service.py +596 -0
  95. claude_mpm/services/agent_modification_tracker.py +841 -0
  96. claude_mpm/services/agent_profile_loader.py +606 -0
  97. claude_mpm/services/agent_registry.py +677 -0
  98. claude_mpm/services/base_agent_manager.py +380 -0
  99. claude_mpm/services/framework_agent_loader.py +337 -0
  100. claude_mpm/services/framework_claude_md_generator/README.md +92 -0
  101. claude_mpm/services/framework_claude_md_generator/__init__.py +206 -0
  102. claude_mpm/services/framework_claude_md_generator/content_assembler.py +151 -0
  103. claude_mpm/services/framework_claude_md_generator/content_validator.py +126 -0
  104. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +137 -0
  105. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +106 -0
  106. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +582 -0
  107. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +97 -0
  108. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +27 -0
  109. claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +23 -0
  110. claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +23 -0
  111. claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +20 -0
  112. claude_mpm/services/framework_claude_md_generator/section_generators/header.py +26 -0
  113. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +30 -0
  114. claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +37 -0
  115. claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +111 -0
  116. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +89 -0
  117. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +39 -0
  118. claude_mpm/services/framework_claude_md_generator/section_manager.py +106 -0
  119. claude_mpm/services/framework_claude_md_generator/version_manager.py +121 -0
  120. claude_mpm/services/framework_claude_md_generator.py +621 -0
  121. claude_mpm/services/hook_service.py +388 -0
  122. claude_mpm/services/hook_service_manager.py +223 -0
  123. claude_mpm/services/json_rpc_hook_manager.py +92 -0
  124. claude_mpm/services/parent_directory_manager/README.md +83 -0
  125. claude_mpm/services/parent_directory_manager/__init__.py +577 -0
  126. claude_mpm/services/parent_directory_manager/backup_manager.py +258 -0
  127. claude_mpm/services/parent_directory_manager/config_manager.py +210 -0
  128. claude_mpm/services/parent_directory_manager/deduplication_manager.py +279 -0
  129. claude_mpm/services/parent_directory_manager/framework_protector.py +143 -0
  130. claude_mpm/services/parent_directory_manager/operations.py +186 -0
  131. claude_mpm/services/parent_directory_manager/state_manager.py +624 -0
  132. claude_mpm/services/parent_directory_manager/template_deployer.py +579 -0
  133. claude_mpm/services/parent_directory_manager/validation_manager.py +378 -0
  134. claude_mpm/services/parent_directory_manager/version_control_helper.py +339 -0
  135. claude_mpm/services/parent_directory_manager/version_manager.py +222 -0
  136. claude_mpm/services/shared_prompt_cache.py +819 -0
  137. claude_mpm/services/ticket_manager.py +213 -0
  138. claude_mpm/services/ticket_manager_di.py +318 -0
  139. claude_mpm/services/ticketing_service_original.py +508 -0
  140. claude_mpm/services/version_control/VERSION +1 -0
  141. claude_mpm/services/version_control/__init__.py +70 -0
  142. claude_mpm/services/version_control/branch_strategy.py +670 -0
  143. claude_mpm/services/version_control/conflict_resolution.py +744 -0
  144. claude_mpm/services/version_control/git_operations.py +784 -0
  145. claude_mpm/services/version_control/semantic_versioning.py +703 -0
  146. claude_mpm/ui/__init__.py +1 -0
  147. claude_mpm/ui/rich_terminal_ui.py +295 -0
  148. claude_mpm/ui/terminal_ui.py +328 -0
  149. claude_mpm/utils/__init__.py +16 -0
  150. claude_mpm/utils/config_manager.py +468 -0
  151. claude_mpm/utils/import_migration_example.py +80 -0
  152. claude_mpm/utils/imports.py +182 -0
  153. claude_mpm/utils/path_operations.py +357 -0
  154. claude_mpm/utils/paths.py +289 -0
  155. claude_mpm-0.3.0.dist-info/METADATA +290 -0
  156. claude_mpm-0.3.0.dist-info/RECORD +159 -0
  157. claude_mpm-0.3.0.dist-info/WHEEL +5 -0
  158. claude_mpm-0.3.0.dist-info/entry_points.txt +4 -0
  159. 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