claude-mpm 4.3.22__py3-none-any.whl → 4.4.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/WORKFLOW.md +2 -14
  3. claude_mpm/cli/commands/configure.py +2 -29
  4. claude_mpm/cli/commands/doctor.py +2 -2
  5. claude_mpm/cli/commands/mpm_init.py +3 -3
  6. claude_mpm/cli/parsers/configure_parser.py +4 -15
  7. claude_mpm/core/framework/__init__.py +38 -0
  8. claude_mpm/core/framework/formatters/__init__.py +11 -0
  9. claude_mpm/core/framework/formatters/capability_generator.py +356 -0
  10. claude_mpm/core/framework/formatters/content_formatter.py +283 -0
  11. claude_mpm/core/framework/formatters/context_generator.py +180 -0
  12. claude_mpm/core/framework/loaders/__init__.py +13 -0
  13. claude_mpm/core/framework/loaders/agent_loader.py +202 -0
  14. claude_mpm/core/framework/loaders/file_loader.py +213 -0
  15. claude_mpm/core/framework/loaders/instruction_loader.py +151 -0
  16. claude_mpm/core/framework/loaders/packaged_loader.py +208 -0
  17. claude_mpm/core/framework/processors/__init__.py +11 -0
  18. claude_mpm/core/framework/processors/memory_processor.py +222 -0
  19. claude_mpm/core/framework/processors/metadata_processor.py +146 -0
  20. claude_mpm/core/framework/processors/template_processor.py +238 -0
  21. claude_mpm/core/framework_loader.py +277 -1798
  22. claude_mpm/hooks/__init__.py +9 -1
  23. claude_mpm/hooks/kuzu_memory_hook.py +352 -0
  24. claude_mpm/hooks/memory_integration_hook.py +1 -1
  25. claude_mpm/services/agents/memory/content_manager.py +5 -2
  26. claude_mpm/services/agents/memory/memory_file_service.py +1 -0
  27. claude_mpm/services/agents/memory/memory_limits_service.py +1 -0
  28. claude_mpm/services/core/path_resolver.py +1 -0
  29. claude_mpm/services/diagnostics/diagnostic_runner.py +1 -0
  30. claude_mpm/services/mcp_config_manager.py +67 -4
  31. claude_mpm/services/mcp_gateway/core/process_pool.py +281 -0
  32. claude_mpm/services/mcp_gateway/core/startup_verification.py +2 -2
  33. claude_mpm/services/mcp_gateway/main.py +3 -13
  34. claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -10
  35. claude_mpm/services/mcp_gateway/tools/__init__.py +13 -2
  36. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +36 -6
  37. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +542 -0
  38. claude_mpm/services/shared/__init__.py +2 -1
  39. claude_mpm/services/shared/service_factory.py +8 -5
  40. claude_mpm/services/unified/__init__.py +65 -0
  41. claude_mpm/services/unified/analyzer_strategies/__init__.py +44 -0
  42. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +473 -0
  43. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +643 -0
  44. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +804 -0
  45. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +661 -0
  46. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +696 -0
  47. claude_mpm/services/unified/config_strategies/__init__.py +190 -0
  48. claude_mpm/services/unified/config_strategies/config_schema.py +689 -0
  49. claude_mpm/services/unified/config_strategies/context_strategy.py +748 -0
  50. claude_mpm/services/unified/config_strategies/error_handling_strategy.py +999 -0
  51. claude_mpm/services/unified/config_strategies/file_loader_strategy.py +871 -0
  52. claude_mpm/services/unified/config_strategies/unified_config_service.py +802 -0
  53. claude_mpm/services/unified/config_strategies/validation_strategy.py +1105 -0
  54. claude_mpm/services/unified/deployment_strategies/__init__.py +97 -0
  55. claude_mpm/services/unified/deployment_strategies/base.py +557 -0
  56. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +486 -0
  57. claude_mpm/services/unified/deployment_strategies/local.py +594 -0
  58. claude_mpm/services/unified/deployment_strategies/utils.py +672 -0
  59. claude_mpm/services/unified/deployment_strategies/vercel.py +471 -0
  60. claude_mpm/services/unified/interfaces.py +499 -0
  61. claude_mpm/services/unified/migration.py +532 -0
  62. claude_mpm/services/unified/strategies.py +551 -0
  63. claude_mpm/services/unified/unified_analyzer.py +534 -0
  64. claude_mpm/services/unified/unified_config.py +688 -0
  65. claude_mpm/services/unified/unified_deployment.py +470 -0
  66. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/METADATA +15 -15
  67. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/RECORD +71 -32
  68. claude_mpm/cli/commands/configure_tui.py +0 -1927
  69. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +0 -645
  70. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +0 -602
  71. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/WHEEL +0 -0
  72. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/entry_points.txt +0 -0
  73. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/licenses/LICENSE +0 -0
  74. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/top_level.txt +0 -0
claude_mpm/VERSION CHANGED
@@ -1 +1 @@
1
- 4.3.22
1
+ 4.4.3
@@ -67,20 +67,8 @@ Return: Clean or list of blocked items
67
67
  **When user mentions**: ticket, epic, issue, task tracking
68
68
 
69
69
  **Process**:
70
- 1. Create ISS (single session) or EP (multi-session)
71
- 2. Create TSK for each phase completed
72
- 3. Update with `aitrackdown comment/transition`
73
-
74
- **Hierarchy**:
75
- ```
76
- EP-0001 (Epic)
77
- └── ISS-0001 (Session Issue)
78
- ├── TSK-0001 (Research)
79
- ├── TSK-0002 (Code Analyzer)
80
- ├── TSK-0003 (Implementation)
81
- ├── TSK-0004 (QA)
82
- └── TSK-0005 (Documentation)
83
- ```
70
+ Use the mcp-ticketer MCP service for ticket management.
71
+ Ticketing is handled through the MCP Gateway, not internal CLI commands.
84
72
 
85
73
  ## Structural Delegation Format
86
74
 
@@ -246,35 +246,8 @@ class ConfigureCommand(BaseCommand):
246
246
  return self._run_interactive_tui(args)
247
247
 
248
248
  def _run_interactive_tui(self, args) -> CommandResult:
249
- """Run the main interactive TUI."""
250
- # Check if we can use the modern Textual TUI
251
- use_textual = getattr(args, "use_textual", True)
252
- force_rich = getattr(args, "force_rich", False)
253
-
254
- if use_textual and not force_rich:
255
- try:
256
- # Try to import and use Textual TUI
257
- from .configure_tui import can_use_tui, launch_tui
258
-
259
- if can_use_tui():
260
- self.console.print(
261
- "[cyan]Launching full-screen configuration interface...[/cyan]"
262
- )
263
- return launch_tui(self.current_scope, self.project_dir)
264
- # Fall back to Rich TUI if terminal doesn't support full-screen
265
- self.console.print(
266
- "[yellow]Terminal doesn't support full-screen mode. Using menu interface.[/yellow]"
267
- )
268
- except ImportError:
269
- # Textual not available, fall back to Rich
270
- self.console.print(
271
- "[yellow]Textual not installed. Using menu interface.[/yellow]"
272
- )
273
- self.console.print(
274
- "[dim]Install textual for a better experience: pip install textual[/dim]"
275
- )
276
-
277
- # Original Rich-based TUI
249
+ """Run the main interactive menu interface."""
250
+ # Rich-based menu interface
278
251
  try:
279
252
  self.console.clear()
280
253
 
@@ -107,8 +107,8 @@ def doctor_command(args):
107
107
  Exit code (0 for success, 1 for warnings, 2 for errors)
108
108
  """
109
109
  # Configure logging
110
- from claude_mpm.core.logging_utils import get_logger
111
- logger = get_logger(__name__)
110
+ from claude_mpm.core.logging_utils import get_logger
111
+ logger = get_logger(__name__)
112
112
 
113
113
  # Determine output format
114
114
  if args.json:
@@ -643,9 +643,9 @@ The final CLAUDE.md should be a comprehensive, well-organized guide that any AI
643
643
  ):
644
644
  checks_passed.append("Archived existing CLAUDE.md")
645
645
 
646
- # Check for issues
647
- if structure_report.get('issues'):
648
- for issue in structure_report['issues']:
646
+ # Check for issues in validation report
647
+ if validation.get('issues'):
648
+ for issue in validation['issues']:
649
649
  warnings.append(issue['description'])
650
650
 
651
651
  if warnings:
@@ -25,11 +25,11 @@ def add_configure_subparser(subparsers) -> argparse.ArgumentParser:
25
25
  Returns:
26
26
  The configured configure subparser
27
27
  """
28
- # Configure command - interactive TUI configuration
28
+ # Configure command - interactive configuration
29
29
  configure_parser = subparsers.add_parser(
30
30
  CLICommands.CONFIGURE.value,
31
- help="Interactive terminal-based configuration interface for managing agents and behaviors",
32
- description="Launch an interactive Rich-based TUI for configuring claude-mpm agents, templates, and behavior files",
31
+ help="Interactive configuration interface for managing agents and behaviors",
32
+ description="Launch an interactive Rich-based menu for configuring claude-mpm agents, templates, and behavior files",
33
33
  )
34
34
 
35
35
  # Add common arguments
@@ -122,21 +122,10 @@ def add_configure_subparser(subparsers) -> argparse.ArgumentParser:
122
122
  # Display options
123
123
  display_group = configure_parser.add_argument_group("display options")
124
124
  display_group.add_argument(
125
- "--no-colors", action="store_true", help="Disable colored output in the TUI"
125
+ "--no-colors", action="store_true", help="Disable colored output in the interface"
126
126
  )
127
127
  display_group.add_argument(
128
128
  "--compact", action="store_true", help="Use compact display mode"
129
129
  )
130
- display_group.add_argument(
131
- "--force-rich",
132
- action="store_true",
133
- help="Force use of Rich menu interface instead of full-screen Textual TUI",
134
- )
135
- display_group.add_argument(
136
- "--no-textual",
137
- dest="use_textual",
138
- action="store_false",
139
- help="Disable Textual TUI and use Rich menu interface",
140
- )
141
130
 
142
131
  return configure_parser
@@ -0,0 +1,38 @@
1
+ """Framework module for Claude MPM.
2
+
3
+ This module provides the modular framework loading system with specialized components
4
+ for handling different aspects of framework initialization and management.
5
+ """
6
+
7
+ from .loaders import (
8
+ AgentLoader,
9
+ FileLoader,
10
+ InstructionLoader,
11
+ PackagedLoader,
12
+ )
13
+ from .formatters import (
14
+ CapabilityGenerator,
15
+ ContentFormatter,
16
+ ContextGenerator,
17
+ )
18
+ from .processors import (
19
+ MemoryProcessor,
20
+ MetadataProcessor,
21
+ TemplateProcessor,
22
+ )
23
+
24
+ __all__ = [
25
+ # Loaders
26
+ "FileLoader",
27
+ "PackagedLoader",
28
+ "InstructionLoader",
29
+ "AgentLoader",
30
+ # Formatters
31
+ "ContentFormatter",
32
+ "CapabilityGenerator",
33
+ "ContextGenerator",
34
+ # Processors
35
+ "MetadataProcessor",
36
+ "TemplateProcessor",
37
+ "MemoryProcessor",
38
+ ]
@@ -0,0 +1,11 @@
1
+ """Framework formatters for content generation and formatting."""
2
+
3
+ from .capability_generator import CapabilityGenerator
4
+ from .content_formatter import ContentFormatter
5
+ from .context_generator import ContextGenerator
6
+
7
+ __all__ = [
8
+ "ContentFormatter",
9
+ "CapabilityGenerator",
10
+ "ContextGenerator",
11
+ ]
@@ -0,0 +1,356 @@
1
+ """Agent capability generator for dynamic agent discovery."""
2
+
3
+ import json
4
+ import time
5
+ from pathlib import Path
6
+ from typing import Any, Dict, List, Optional
7
+
8
+ import yaml
9
+
10
+ from claude_mpm.core.logging_utils import get_logger
11
+
12
+ # Import resource handling for packaged installations
13
+ try:
14
+ from importlib.resources import files
15
+ except ImportError:
16
+ try:
17
+ from importlib_resources import files
18
+ except ImportError:
19
+ files = None
20
+
21
+
22
+ class CapabilityGenerator:
23
+ """Generates agent capability sections from deployed agents."""
24
+
25
+ def __init__(self):
26
+ """Initialize the capability generator."""
27
+ self.logger = get_logger("capability_generator")
28
+
29
+ def generate_capabilities_section(
30
+ self,
31
+ deployed_agents: List[Dict[str, Any]],
32
+ local_agents: Dict[str, Dict[str, Any]],
33
+ ) -> str:
34
+ """Generate dynamic agent capabilities section.
35
+
36
+ Args:
37
+ deployed_agents: List of deployed agent metadata
38
+ local_agents: Dictionary of local JSON template agents
39
+
40
+ Returns:
41
+ Formatted capabilities section string
42
+ """
43
+ # Build capabilities section
44
+ section = "\n\n## Available Agent Capabilities\n\n"
45
+
46
+ # Combine deployed and local agents
47
+ all_agents = {} # key: agent_id, value: (agent_data, priority)
48
+
49
+ # Add local agents first (highest priority)
50
+ for agent_id, agent_data in local_agents.items():
51
+ all_agents[agent_id] = (agent_data, -1) # Priority -1 for local agents
52
+
53
+ # Add deployed agents with lower priority
54
+ for priority, agent_data in enumerate(deployed_agents):
55
+ agent_id = agent_data["id"]
56
+ # Only add if not already present
57
+ if agent_id not in all_agents:
58
+ all_agents[agent_id] = (agent_data, priority)
59
+
60
+ # Extract just the agent data and sort
61
+ final_agents = [agent_data for agent_data, _ in all_agents.values()]
62
+
63
+ if not final_agents:
64
+ return self.get_fallback_capabilities()
65
+
66
+ # Sort agents alphabetically by ID
67
+ final_agents.sort(key=lambda x: x["id"])
68
+
69
+ # Display all agents with their rich descriptions
70
+ for agent in final_agents:
71
+ # Clean up display name - handle common acronyms
72
+ display_name = agent.get("display_name", agent["id"])
73
+ display_name = (
74
+ display_name.replace("Qa ", "QA ")
75
+ .replace("Ui ", "UI ")
76
+ .replace("Api ", "API ")
77
+ )
78
+ if display_name.lower() == "qa agent":
79
+ display_name = "QA Agent"
80
+
81
+ # Add local indicator if this is a local agent
82
+ if agent.get("is_local"):
83
+ tier_label = f" [LOCAL-{agent.get('tier', 'PROJECT').upper()}]"
84
+ section += f"\n### {display_name} (`{agent['id']}`) {tier_label}\n"
85
+ else:
86
+ section += f"\n### {display_name} (`{agent['id']}`)\n"
87
+
88
+ section += f"{agent.get('description', 'Specialized agent')}\n"
89
+
90
+ # Add routing information if available
91
+ if agent.get("routing"):
92
+ routing = agent["routing"]
93
+ routing_hints = []
94
+
95
+ if routing.get("keywords"):
96
+ # Show first 5 keywords for brevity
97
+ keywords = routing["keywords"][:5]
98
+ routing_hints.append(f"Keywords: {', '.join(keywords)}")
99
+
100
+ if routing.get("paths"):
101
+ # Show first 3 paths for brevity
102
+ paths = routing["paths"][:3]
103
+ routing_hints.append(f"Paths: {', '.join(paths)}")
104
+
105
+ if routing.get("priority"):
106
+ routing_hints.append(f"Priority: {routing['priority']}")
107
+
108
+ if routing_hints:
109
+ section += f"- **Routing**: {' | '.join(routing_hints)}\n"
110
+
111
+ # Add when_to_use if present
112
+ if routing.get("when_to_use"):
113
+ section += f"- **When to use**: {routing['when_to_use']}\n"
114
+
115
+ # Add any additional metadata if present
116
+ if agent.get("authority"):
117
+ section += f"- **Authority**: {agent['authority']}\n"
118
+ if agent.get("primary_function"):
119
+ section += f"- **Primary Function**: {agent['primary_function']}\n"
120
+ if agent.get("handoff_to"):
121
+ section += f"- **Handoff To**: {agent['handoff_to']}\n"
122
+ if agent.get("tools") and agent["tools"] != "standard":
123
+ section += f"- **Tools**: {agent['tools']}\n"
124
+ if agent.get("model") and agent["model"] != "opus":
125
+ section += f"- **Model**: {agent['model']}\n"
126
+
127
+ # Add memory routing information if available
128
+ if agent.get("memory_routing"):
129
+ memory_routing = agent["memory_routing"]
130
+ if memory_routing.get("description"):
131
+ section += f"- **Memory Routing**: {memory_routing['description']}\n"
132
+
133
+ # Add simple Context-Aware Agent Selection
134
+ section += "\n## Context-Aware Agent Selection\n\n"
135
+ section += "Select agents based on their descriptions above. Key principles:\n"
136
+ section += "- **PM questions** → Answer directly (only exception)\n"
137
+ section += "- Match task requirements to agent descriptions and authority\n"
138
+ section += "- Consider agent handoff recommendations\n"
139
+ section += "- Use the agent ID in parentheses when delegating via Task tool\n"
140
+
141
+ # Add summary
142
+ section += f"\n**Total Available Agents**: {len(final_agents)}\n"
143
+
144
+ return section
145
+
146
+ def parse_agent_metadata(self, agent_file: Path) -> Optional[Dict[str, Any]]:
147
+ """Parse agent metadata from deployed agent file.
148
+
149
+ Args:
150
+ agent_file: Path to deployed agent file
151
+
152
+ Returns:
153
+ Dictionary with agent metadata or None
154
+ """
155
+ try:
156
+ with open(agent_file) as f:
157
+ content = f.read()
158
+
159
+ # Default values
160
+ agent_data = {
161
+ "id": agent_file.stem,
162
+ "display_name": agent_file.stem.replace("_", " ").replace("-", " ").title(),
163
+ "description": "Specialized agent",
164
+ }
165
+
166
+ # Extract YAML frontmatter if present
167
+ if content.startswith("---"):
168
+ end_marker = content.find("---", 3)
169
+ if end_marker > 0:
170
+ frontmatter = content[3:end_marker]
171
+ metadata = yaml.safe_load(frontmatter)
172
+ if metadata:
173
+ # Use name as ID for Task tool
174
+ agent_data["id"] = metadata.get("name", agent_data["id"])
175
+ agent_data["display_name"] = (
176
+ metadata.get("name", agent_data["display_name"])
177
+ .replace("-", " ")
178
+ .title()
179
+ )
180
+
181
+ # Copy all metadata fields directly
182
+ for key, value in metadata.items():
183
+ if key not in ["name"]: # Skip already processed fields
184
+ agent_data[key] = value
185
+
186
+ # Try to load routing metadata from JSON template if not in YAML frontmatter
187
+ if "routing" not in agent_data:
188
+ routing_data = self.load_routing_from_template(agent_file.stem)
189
+ if routing_data:
190
+ agent_data["routing"] = routing_data
191
+
192
+ # Try to load memory routing metadata from JSON template
193
+ if "memory_routing" not in agent_data:
194
+ memory_routing_data = self.load_memory_routing_from_template(agent_file.stem)
195
+ if memory_routing_data:
196
+ agent_data["memory_routing"] = memory_routing_data
197
+
198
+ return agent_data
199
+
200
+ except Exception as e:
201
+ self.logger.debug(f"Could not parse metadata from {agent_file}: {e}")
202
+ return None
203
+
204
+ def load_routing_from_template(
205
+ self, agent_name: str, framework_path: Optional[Path] = None
206
+ ) -> Optional[Dict[str, Any]]:
207
+ """Load routing metadata from agent JSON template.
208
+
209
+ Args:
210
+ agent_name: Name of the agent
211
+ framework_path: Path to framework installation
212
+
213
+ Returns:
214
+ Dictionary with routing metadata or None if not found
215
+ """
216
+ try:
217
+ # Check if we have a framework path
218
+ if not framework_path or framework_path == Path("__PACKAGED__"):
219
+ # For packaged installations, try to load from package resources
220
+ if files:
221
+ try:
222
+ templates_package = files("claude_mpm.agents.templates")
223
+ template_file = templates_package / f"{agent_name}.json"
224
+
225
+ if template_file.is_file():
226
+ template_content = template_file.read_text()
227
+ template_data = json.loads(template_content)
228
+ return template_data.get("routing")
229
+ except Exception as e:
230
+ self.logger.debug(
231
+ f"Could not load routing from packaged template for {agent_name}: {e}"
232
+ )
233
+ return None
234
+
235
+ # For development mode, load from filesystem
236
+ templates_dir = framework_path / "src" / "claude_mpm" / "agents" / "templates"
237
+ template_file = templates_dir / f"{agent_name}.json"
238
+
239
+ if template_file.exists():
240
+ with open(template_file) as f:
241
+ template_data = json.load(f)
242
+ return template_data.get("routing")
243
+
244
+ # Also check for variations in naming (underscore vs dash)
245
+ alternative_names = list(
246
+ {
247
+ agent_name.replace("-", "_"),
248
+ agent_name.replace("_", "-"),
249
+ agent_name.replace("-", ""),
250
+ agent_name.replace("_", ""),
251
+ }
252
+ )
253
+
254
+ for alt_name in alternative_names:
255
+ if alt_name != agent_name:
256
+ alt_file = templates_dir / f"{alt_name}.json"
257
+ if alt_file.exists():
258
+ with open(alt_file) as f:
259
+ template_data = json.load(f)
260
+ return template_data.get("routing")
261
+
262
+ return None
263
+
264
+ except Exception as e:
265
+ self.logger.debug(f"Could not load routing metadata for {agent_name}: {e}")
266
+ return None
267
+
268
+ def load_memory_routing_from_template(
269
+ self, agent_name: str, framework_path: Optional[Path] = None
270
+ ) -> Optional[Dict[str, Any]]:
271
+ """Load memory routing metadata from agent JSON template.
272
+
273
+ Args:
274
+ agent_name: Name of the agent
275
+ framework_path: Path to framework installation
276
+
277
+ Returns:
278
+ Dictionary with memory routing metadata or None if not found
279
+ """
280
+ try:
281
+ # Check if we have a framework path
282
+ if not framework_path or framework_path == Path("__PACKAGED__"):
283
+ # For packaged installations, try to load from package resources
284
+ if files:
285
+ try:
286
+ templates_package = files("claude_mpm.agents.templates")
287
+ template_file = templates_package / f"{agent_name}.json"
288
+
289
+ if template_file.is_file():
290
+ template_content = template_file.read_text()
291
+ template_data = json.loads(template_content)
292
+ return template_data.get("memory_routing")
293
+ except Exception as e:
294
+ self.logger.debug(
295
+ f"Could not load memory routing from packaged template for {agent_name}: {e}"
296
+ )
297
+ return None
298
+
299
+ # For development mode, load from filesystem
300
+ templates_dir = framework_path / "src" / "claude_mpm" / "agents" / "templates"
301
+ template_file = templates_dir / f"{agent_name}.json"
302
+
303
+ if template_file.exists():
304
+ with open(template_file) as f:
305
+ template_data = json.load(f)
306
+ return template_data.get("memory_routing")
307
+
308
+ # Also check for variations in naming
309
+ alternative_names = list(
310
+ {
311
+ agent_name.replace("-", "_"),
312
+ agent_name.replace("_", "-"),
313
+ agent_name.replace("-agent", ""),
314
+ agent_name.replace("_agent", ""),
315
+ agent_name + "_agent",
316
+ agent_name + "-agent",
317
+ }
318
+ )
319
+
320
+ for alt_name in alternative_names:
321
+ if alt_name != agent_name:
322
+ alt_file = templates_dir / f"{alt_name}.json"
323
+ if alt_file.exists():
324
+ with open(alt_file) as f:
325
+ template_data = json.load(f)
326
+ return template_data.get("memory_routing")
327
+
328
+ return None
329
+
330
+ except Exception as e:
331
+ self.logger.debug(f"Could not load memory routing from template for {agent_name}: {e}")
332
+ return None
333
+
334
+ def get_fallback_capabilities(self) -> str:
335
+ """Return fallback capabilities when dynamic discovery fails.
336
+
337
+ Returns:
338
+ Fallback agent capabilities section
339
+ """
340
+ return """
341
+
342
+ ## Available Agent Capabilities
343
+
344
+ You have the following specialized agents available for delegation:
345
+
346
+ - **Engineer** (`engineer`): Code implementation and development
347
+ - **Research** (`research-agent`): Investigation and analysis
348
+ - **QA** (`qa-agent`): Testing and quality assurance
349
+ - **Documentation** (`documentation-agent`): Documentation creation and maintenance
350
+ - **Security** (`security-agent`): Security analysis and protection
351
+ - **Data Engineer** (`data-engineer`): Data management and pipelines
352
+ - **Ops** (`ops-agent`): Deployment and operations
353
+ - **Version Control** (`version-control`): Git operations and version management
354
+
355
+ **IMPORTANT**: Use the exact agent ID in parentheses when delegating tasks.
356
+ """