claude-mpm 0.3.0__py3-none-any.whl → 1.1.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.
Files changed (42) hide show
  1. claude_mpm/_version.py +3 -2
  2. claude_mpm/agents/INSTRUCTIONS.md +23 -0
  3. claude_mpm/agents/__init__.py +2 -2
  4. claude_mpm/agents/agent-template.yaml +83 -0
  5. claude_mpm/agents/agent_loader.py +66 -90
  6. claude_mpm/agents/base_agent_loader.py +10 -15
  7. claude_mpm/cli.py +41 -47
  8. claude_mpm/cli_enhancements.py +297 -0
  9. claude_mpm/core/agent_name_normalizer.py +49 -0
  10. claude_mpm/core/factories.py +1 -46
  11. claude_mpm/core/service_registry.py +0 -8
  12. claude_mpm/core/simple_runner.py +50 -0
  13. claude_mpm/generators/__init__.py +5 -0
  14. claude_mpm/generators/agent_profile_generator.py +137 -0
  15. claude_mpm/hooks/README.md +75 -221
  16. claude_mpm/hooks/builtin/mpm_command_hook.py +125 -0
  17. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +8 -7
  18. claude_mpm/hooks/claude_hooks/__init__.py +5 -0
  19. claude_mpm/hooks/claude_hooks/hook_handler.py +399 -0
  20. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +47 -0
  21. claude_mpm/hooks/validation_hooks.py +181 -0
  22. claude_mpm/services/agent_management_service.py +4 -4
  23. claude_mpm/services/agent_profile_loader.py +1 -1
  24. claude_mpm/services/agent_registry.py +0 -1
  25. claude_mpm/services/base_agent_manager.py +3 -3
  26. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +57 -31
  27. claude_mpm/utils/error_handler.py +247 -0
  28. claude_mpm/validation/__init__.py +5 -0
  29. claude_mpm/validation/agent_validator.py +175 -0
  30. {claude_mpm-0.3.0.dist-info → claude_mpm-1.1.0.dist-info}/METADATA +44 -7
  31. {claude_mpm-0.3.0.dist-info → claude_mpm-1.1.0.dist-info}/RECORD +34 -30
  32. claude_mpm/config/hook_config.py +0 -42
  33. claude_mpm/hooks/hook_client.py +0 -264
  34. claude_mpm/hooks/hook_runner.py +0 -370
  35. claude_mpm/hooks/json_rpc_executor.py +0 -259
  36. claude_mpm/hooks/json_rpc_hook_client.py +0 -319
  37. claude_mpm/services/hook_service.py +0 -388
  38. claude_mpm/services/hook_service_manager.py +0 -223
  39. claude_mpm/services/json_rpc_hook_manager.py +0 -92
  40. {claude_mpm-0.3.0.dist-info → claude_mpm-1.1.0.dist-info}/WHEEL +0 -0
  41. {claude_mpm-0.3.0.dist-info → claude_mpm-1.1.0.dist-info}/entry_points.txt +0 -0
  42. {claude_mpm-0.3.0.dist-info → claude_mpm-1.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,297 @@
1
+ """
2
+ Enhanced CLI operations for claude-mpm.
3
+
4
+ Implements error handling and user guidance patterns from awesome-claude-code.
5
+ """
6
+
7
+ import click
8
+ import sys
9
+ import logging
10
+ from typing import Optional, Any, Dict, List
11
+ from pathlib import Path
12
+
13
+ from claude_mpm.utils.error_handler import (
14
+ MPMError, ErrorContext, handle_errors, suggest_setup_fix
15
+ )
16
+ from claude_mpm.validation import AgentValidator, ValidationResult
17
+ from claude_mpm.hooks.validation_hooks import ValidationHooks, ValidationError
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class CLIContext:
23
+ """Enhanced CLI context with validation and error handling."""
24
+
25
+ def __init__(self):
26
+ """Initialize CLI context."""
27
+ self.validator = AgentValidator()
28
+ self.validation_hooks = ValidationHooks()
29
+ self.debug = False
30
+ self.dry_run = False
31
+
32
+ def setup_logging(self, debug: bool = False) -> None:
33
+ """Setup logging based on debug flag."""
34
+ level = logging.DEBUG if debug else logging.INFO
35
+ format_str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" if debug else "%(message)s"
36
+
37
+ logging.basicConfig(
38
+ level=level,
39
+ format=format_str,
40
+ handlers=[logging.StreamHandler(sys.stdout)]
41
+ )
42
+ self.debug = debug
43
+
44
+ def validate_prerequisites(self) -> bool:
45
+ """Validate system prerequisites."""
46
+ print("Checking prerequisites...")
47
+ all_passed = True
48
+
49
+ # Check Python version
50
+ if sys.version_info < (3, 11):
51
+ print("❌ Python 3.11 or higher is required")
52
+ all_passed = False
53
+ else:
54
+ print("✓ Python version is compatible")
55
+
56
+ # Check required directories
57
+ required_dirs = [
58
+ Path.home() / '.claude-mpm',
59
+ Path.home() / '.claude-mpm' / 'profiles',
60
+ Path.home() / '.claude-mpm' / 'logs',
61
+ ]
62
+
63
+ for dir_path in required_dirs:
64
+ if not dir_path.exists():
65
+ try:
66
+ dir_path.mkdir(parents=True)
67
+ print(f"✓ Created directory: {dir_path}")
68
+ except Exception as e:
69
+ print(f"❌ Failed to create directory {dir_path}: {e}")
70
+ all_passed = False
71
+
72
+ return all_passed
73
+
74
+ def handle_validation_result(self, result: ValidationResult,
75
+ operation: str = "operation") -> None:
76
+ """Handle validation results with user-friendly output."""
77
+ if result.is_valid:
78
+ if result.warnings:
79
+ print(f"⚠️ {operation} completed with warnings:")
80
+ for warning in result.warnings:
81
+ print(f" - {warning}")
82
+ else:
83
+ print(f"✅ {operation} completed successfully")
84
+ else:
85
+ print(f"❌ {operation} failed validation:")
86
+ for error in result.errors:
87
+ print(f" - {error}")
88
+
89
+ if result.warnings:
90
+ print("\nAdditional warnings:")
91
+ for warning in result.warnings:
92
+ print(f" - {warning}")
93
+
94
+ sys.exit(1)
95
+
96
+
97
+ def create_enhanced_cli() -> click.Group:
98
+ """Create enhanced CLI with better error handling."""
99
+ cli_context = CLIContext()
100
+
101
+ @click.group()
102
+ @click.option('--debug', is_flag=True, help='Enable debug logging')
103
+ @click.option('--dry-run', is_flag=True, help='Run without making changes')
104
+ @click.pass_context
105
+ def cli(ctx, debug: bool, dry_run: bool):
106
+ """Enhanced claude-mpm CLI with validation and error handling."""
107
+ ctx.obj = cli_context
108
+ cli_context.setup_logging(debug)
109
+ cli_context.dry_run = dry_run
110
+
111
+ if debug:
112
+ print("🐛 Debug mode enabled")
113
+ if dry_run:
114
+ print("🏃 Dry-run mode enabled")
115
+
116
+ @cli.command()
117
+ @click.pass_context
118
+ def validate_setup(ctx):
119
+ """Validate system setup and prerequisites."""
120
+ cli_ctx = ctx.obj
121
+
122
+ with ErrorContext("setup validation"):
123
+ if cli_ctx.validate_prerequisites():
124
+ print("\n✅ All prerequisites satisfied!")
125
+ print(" You're ready to use claude-mpm")
126
+ else:
127
+ print("\n❌ Setup validation failed")
128
+ print("\n💡 Setup hints:")
129
+ print(" - Ensure Python 3.11+ is installed")
130
+ print(" - Check file permissions for ~/.claude-mpm")
131
+ print(" - Run with --debug for more details")
132
+ sys.exit(1)
133
+
134
+ @cli.command()
135
+ @click.argument('profile_path', type=click.Path(exists=True))
136
+ @click.pass_context
137
+ @handle_errors(MPMError)
138
+ async def validate_profile(ctx, profile_path: str):
139
+ """Validate an agent profile."""
140
+ cli_ctx = ctx.obj
141
+ profile = Path(profile_path)
142
+
143
+ print(f"Validating profile: {profile.name}")
144
+
145
+ # Run pre-load validation
146
+ result = await cli_ctx.validation_hooks.run_pre_load_validation(profile)
147
+
148
+ cli_ctx.handle_validation_result(result, f"Profile validation for {profile.name}")
149
+
150
+ if result.is_valid and result.locked_fields:
151
+ print(f"\n🔒 Locked fields: {', '.join(result.locked_fields)}")
152
+
153
+ @cli.command()
154
+ @click.option('--profile', '-p', help='Agent profile to load')
155
+ @click.option('--task', '-t', help='Task to execute')
156
+ @click.pass_context
157
+ @handle_errors(MPMError)
158
+ async def run_agent(ctx, profile: str, task: str):
159
+ """Run an agent with enhanced validation."""
160
+ cli_ctx = ctx.obj
161
+
162
+ if not profile or not task:
163
+ raise click.UsageError("Both --profile and --task are required")
164
+
165
+ # Validate profile exists
166
+ profile_path = Path(profile)
167
+ if not profile_path.exists():
168
+ # Try default locations
169
+ default_locations = [
170
+ Path.home() / '.claude-mpm' / 'profiles' / f"{profile}.yaml",
171
+ Path.cwd() / 'agents' / f"{profile}.yaml",
172
+ ]
173
+
174
+ for location in default_locations:
175
+ if location.exists():
176
+ profile_path = location
177
+ break
178
+ else:
179
+ raise MPMError(
180
+ f"Profile '{profile}' not found",
181
+ details={'searched_locations': [str(p) for p in default_locations]},
182
+ suggestions=[
183
+ "Check the profile name",
184
+ "Use 'mpm list-profiles' to see available profiles",
185
+ "Create a new profile with 'mpm create-profile'"
186
+ ]
187
+ )
188
+
189
+ # Run validation
190
+ print(f"🔍 Validating profile: {profile_path.name}")
191
+ validation_result = await cli_ctx.validation_hooks.run_pre_load_validation(profile_path)
192
+
193
+ if not validation_result.is_valid:
194
+ cli_ctx.handle_validation_result(validation_result, "Profile validation")
195
+ return
196
+
197
+ # Validate task
198
+ print(f"🔍 Validating task...")
199
+ task_result = await cli_ctx.validation_hooks.run_pre_execute_validation(
200
+ profile_path.stem, task
201
+ )
202
+
203
+ if not task_result.is_valid:
204
+ cli_ctx.handle_validation_result(task_result, "Task validation")
205
+ return
206
+
207
+ if cli_ctx.dry_run:
208
+ print("\n🏃 Dry-run mode - would execute:")
209
+ print(f" Profile: {profile_path}")
210
+ print(f" Task: {task[:100]}{'...' if len(task) > 100 else ''}")
211
+ else:
212
+ print(f"\n🚀 Executing agent from {profile_path.name}...")
213
+ # Actual execution would go here
214
+ print("✅ Agent execution completed")
215
+
216
+ @cli.command()
217
+ @click.pass_context
218
+ def list_profiles(ctx):
219
+ """List available agent profiles."""
220
+ cli_ctx = ctx.obj
221
+
222
+ profile_locations = [
223
+ Path.home() / '.claude-mpm' / 'profiles',
224
+ Path.cwd() / 'agents',
225
+ ]
226
+
227
+ all_profiles = []
228
+ for location in profile_locations:
229
+ if location.exists():
230
+ profiles = list(location.glob('*.yaml')) + list(location.glob('*.yml'))
231
+ all_profiles.extend(profiles)
232
+
233
+ if not all_profiles:
234
+ print("No agent profiles found")
235
+ print("\n💡 Create a profile with: mpm create-profile")
236
+ return
237
+
238
+ print("Available agent profiles:")
239
+ for profile in sorted(all_profiles):
240
+ # Quick validation check
241
+ result = cli_ctx.validator.validate_profile(profile)
242
+ status = "✅" if result.is_valid else "⚠️"
243
+ print(f" {status} {profile.stem} ({profile})")
244
+
245
+ @cli.command()
246
+ @click.argument('name')
247
+ @click.option('--role', '-r', required=True, help='Agent role')
248
+ @click.option('--category', '-c', default='analysis', help='Agent category')
249
+ @click.pass_context
250
+ def create_profile(ctx, name: str, role: str, category: str):
251
+ """Create a new agent profile from template."""
252
+ from claude_mpm.generators import AgentProfileGenerator
253
+
254
+ cli_ctx = ctx.obj
255
+ generator = AgentProfileGenerator()
256
+
257
+ print(f"Creating agent profile: {name}")
258
+
259
+ # Generate configuration
260
+ config = generator.create_agent_from_template(name, role, category)
261
+
262
+ # Generate profile
263
+ profile_content = generator.generate_profile(config)
264
+
265
+ # Save profile
266
+ profile_dir = Path.home() / '.claude-mpm' / 'profiles'
267
+ profile_dir.mkdir(parents=True, exist_ok=True)
268
+
269
+ profile_path = profile_dir / f"{name.lower().replace(' ', '_')}.yaml"
270
+
271
+ if profile_path.exists() and not click.confirm(f"Profile {profile_path} exists. Overwrite?"):
272
+ print("Aborted")
273
+ return
274
+
275
+ if cli_ctx.dry_run:
276
+ print(f"\n🏃 Dry-run mode - would create {profile_path}:")
277
+ print("---")
278
+ print(profile_content[:500] + "..." if len(profile_content) > 500 else profile_content)
279
+ print("---")
280
+ else:
281
+ profile_path.write_text(profile_content)
282
+ print(f"✅ Created profile: {profile_path}")
283
+
284
+ # Generate documentation
285
+ doc_path = profile_path.with_suffix('.md')
286
+ doc_content = generator.generate_agent_documentation(config)
287
+ doc_path.write_text(doc_content)
288
+ print(f"📝 Created documentation: {doc_path}")
289
+
290
+ return cli
291
+
292
+
293
+ # Export the enhanced CLI
294
+ enhanced_cli = create_enhanced_cli()
295
+
296
+ if __name__ == '__main__':
297
+ enhanced_cli()
@@ -242,6 +242,55 @@ class AgentNameNormalizer:
242
242
  return False, f"Unknown agent '{agent}'. Valid agents: {', '.join(cls.CANONICAL_NAMES.values())}"
243
243
 
244
244
  return True, None
245
+
246
+ @classmethod
247
+ def to_task_format(cls, agent_name: str) -> str:
248
+ """
249
+ Convert agent name from TodoWrite format to Task tool format.
250
+
251
+ Args:
252
+ agent_name: The agent name in TodoWrite format (e.g., "Research", "Version Control")
253
+
254
+ Returns:
255
+ The agent name in Task tool format (e.g., "research", "version-control")
256
+
257
+ Examples:
258
+ "Research" → "research"
259
+ "Version Control" → "version-control"
260
+ "Data Engineer" → "data-engineer"
261
+ "QA" → "qa"
262
+ """
263
+ # First normalize to canonical form
264
+ normalized = cls.normalize(agent_name)
265
+ # Convert to lowercase and replace spaces with hyphens
266
+ return normalized.lower().replace(" ", "-")
267
+
268
+ @classmethod
269
+ def from_task_format(cls, task_format: str) -> str:
270
+ """
271
+ Convert agent name from Task tool format to TodoWrite format.
272
+
273
+ Args:
274
+ task_format: The agent name in Task tool format (e.g., "research", "version-control")
275
+
276
+ Returns:
277
+ The agent name in TodoWrite format (e.g., "Research", "Version Control")
278
+
279
+ Examples:
280
+ "research" → "Research"
281
+ "version-control" → "Version Control"
282
+ "data-engineer" → "Data Engineer"
283
+ "qa" → "QA"
284
+ """
285
+ # Replace hyphens with underscores for lookup
286
+ lookup_key = task_format.replace("-", "_")
287
+
288
+ # Check if it's a valid canonical key
289
+ if lookup_key in cls.CANONICAL_NAMES:
290
+ return cls.CANONICAL_NAMES[lookup_key]
291
+
292
+ # Try normalizing as-is
293
+ return cls.normalize(task_format)
245
294
 
246
295
 
247
296
  # Global instance for easy access
@@ -12,7 +12,6 @@ from typing import Any, Dict, Optional, Type, TypeVar
12
12
  from .container import DIContainer
13
13
  from .logger import get_logger
14
14
  from .config import Config
15
- from ..services.json_rpc_hook_manager import JSONRPCHookManager
16
15
  from ..services.agent_deployment import AgentDeploymentService
17
16
  from ..orchestration.factory import OrchestratorFactory
18
17
  from ..orchestration.base import BaseOrchestrator
@@ -31,47 +30,6 @@ class ServiceFactory(ABC):
31
30
  pass
32
31
 
33
32
 
34
- class HookManagerFactory(ServiceFactory):
35
- """Factory for creating hook manager instances."""
36
-
37
- def create(
38
- self,
39
- container: DIContainer,
40
- log_dir: Optional[Path] = None,
41
- enabled: bool = True
42
- ) -> Optional[JSONRPCHookManager]:
43
- """
44
- Create a hook manager instance.
45
-
46
- Args:
47
- container: DI container
48
- log_dir: Log directory path
49
- enabled: Whether hooks are enabled
50
-
51
- Returns:
52
- Hook manager instance or None if disabled
53
- """
54
- config = container.resolve(Config)
55
-
56
- # Check if hooks are enabled
57
- if not enabled or config.get('hooks.enabled', True) is False:
58
- logger.info("Hooks are disabled")
59
- return None
60
-
61
- # Get log directory from config if not provided
62
- if log_dir is None:
63
- log_dir = Path(config.get('log_dir', '.claude-mpm/logs'))
64
-
65
- # Create hook manager
66
- hook_manager = JSONRPCHookManager(log_dir=log_dir)
67
-
68
- # Start the service
69
- if hook_manager.start_service():
70
- logger.info("Hook manager initialized successfully")
71
- return hook_manager
72
- else:
73
- logger.warning("Failed to start hook manager")
74
- return None
75
33
 
76
34
 
77
35
  class OrchestratorFactoryWrapper(ServiceFactory):
@@ -110,9 +68,7 @@ class OrchestratorFactoryWrapper(ServiceFactory):
110
68
  # Merge with provided kwargs
111
69
  orch_config.update(kwargs)
112
70
 
113
- # Inject services into orchestrator config
114
- if 'hook_manager' not in orch_config and container.is_registered(JSONRPCHookManager):
115
- orch_config['hook_manager'] = container.resolve_optional(JSONRPCHookManager)
71
+ # No hook manager injection needed - Claude Code hooks are external
116
72
 
117
73
  # Create orchestrator
118
74
  orchestrator = self._factory.create_orchestrator(
@@ -248,7 +204,6 @@ class FactoryRegistry:
248
204
  def __init__(self):
249
205
  """Initialize factory registry."""
250
206
  self._factories: Dict[str, ServiceFactory] = {
251
- 'hook_manager': HookManagerFactory(),
252
207
  'orchestrator': OrchestratorFactoryWrapper(),
253
208
  'agent_service': AgentServiceFactory(),
254
209
  'session_manager': SessionManagerFactory(),
@@ -37,7 +37,6 @@ class ServiceRegistry:
37
37
  def register_core_services(self) -> None:
38
38
  """Register all core framework services."""
39
39
  from ..services.shared_prompt_cache import SharedPromptCache
40
- from ..services.json_rpc_hook_manager import JSONRPCHookManager
41
40
  from ..services.ticket_manager import TicketManager
42
41
  from ..services.agent_deployment import AgentDeploymentService
43
42
  from .session_manager import SessionManager
@@ -72,13 +71,6 @@ class ServiceRegistry:
72
71
  factory=lambda c: SharedPromptCache.get_instance()
73
72
  )
74
73
 
75
- # Register hook manager
76
- self.register_service(
77
- "hook_manager",
78
- JSONRPCHookManager,
79
- lifetime=ServiceLifetime.SINGLETON
80
- )
81
-
82
74
  # Register ticket manager
83
75
  self.register_service(
84
76
  "ticket_manager",
@@ -254,6 +254,10 @@ class SimpleClaudeRunner:
254
254
  """Run Claude with a single prompt and return success status."""
255
255
  start_time = time.time()
256
256
 
257
+ # Check for /mpm: commands
258
+ if prompt.strip().startswith("/mpm:"):
259
+ return self._handle_mpm_command(prompt.strip())
260
+
257
261
  if self.project_logger:
258
262
  self.project_logger.log_system(
259
263
  f"Starting non-interactive session with prompt: {prompt[:100]}",
@@ -460,6 +464,45 @@ class SimpleClaudeRunner:
460
464
  text_lower = text.lower()
461
465
  return any(pattern.lower() in text_lower for pattern in delegation_patterns)
462
466
 
467
+ def _handle_mpm_command(self, prompt: str) -> bool:
468
+ """Handle /mpm: commands directly without going to Claude."""
469
+ try:
470
+ # Extract command and arguments
471
+ command_line = prompt[5:].strip() # Remove "/mpm:"
472
+ parts = command_line.split()
473
+
474
+ if not parts:
475
+ print("No command specified. Available commands: test")
476
+ return True
477
+
478
+ command = parts[0]
479
+ args = parts[1:]
480
+
481
+ # Handle commands
482
+ if command == "test":
483
+ print("Hello World")
484
+ if self.project_logger:
485
+ self.project_logger.log_system(
486
+ "Executed /mpm:test command",
487
+ level="INFO",
488
+ component="command"
489
+ )
490
+ return True
491
+ else:
492
+ print(f"Unknown command: {command}")
493
+ print("Available commands: test")
494
+ return True
495
+
496
+ except Exception as e:
497
+ print(f"Error executing command: {e}")
498
+ if self.project_logger:
499
+ self.project_logger.log_system(
500
+ f"Failed to execute /mpm: command: {e}",
501
+ level="ERROR",
502
+ component="command"
503
+ )
504
+ return False
505
+
463
506
  def _log_session_event(self, event_data: dict):
464
507
  """Log an event to the session log file."""
465
508
  if self.session_log_file:
@@ -491,6 +534,13 @@ You have access to native subagents via the Task tool with subagent_type paramet
491
534
 
492
535
  Use these agents by calling: Task(description="task description", subagent_type="agent_name")
493
536
 
537
+ IMPORTANT: The Task tool accepts both naming formats:
538
+ - Capitalized format: "Research", "Engineer", "QA", "Version Control", "Data Engineer"
539
+ - Lowercase format: "research", "engineer", "qa", "version-control", "data-engineer"
540
+
541
+ Both formats work correctly. When you see capitalized names (matching TodoWrite prefixes),
542
+ automatically normalize them to lowercase-hyphenated format for the Task tool.
543
+
494
544
  Work efficiently and delegate appropriately to subagents when needed."""
495
545
 
496
546
 
@@ -0,0 +1,5 @@
1
+ """Code and documentation generators for claude-mpm."""
2
+
3
+ from .agent_profile_generator import AgentProfileGenerator
4
+
5
+ __all__ = ['AgentProfileGenerator']
@@ -0,0 +1,137 @@
1
+ """
2
+ Agent profile generator using template system.
3
+
4
+ Inspired by awesome-claude-code's template generation approach.
5
+ """
6
+
7
+ import yaml
8
+ from pathlib import Path
9
+ from datetime import datetime
10
+ from typing import Dict, Any, Optional
11
+ import re
12
+
13
+
14
+ class AgentProfileGenerator:
15
+ """Generates agent profiles from templates."""
16
+
17
+ def __init__(self, template_path: Optional[Path] = None):
18
+ """Initialize the generator with a template path."""
19
+ self.template_path = template_path or Path(__file__).parent.parent / "agents" / "agent-template.yaml"
20
+ self.template = self._load_template()
21
+
22
+ def _load_template(self) -> Dict[str, Any]:
23
+ """Load the agent profile template."""
24
+ if not self.template_path.exists():
25
+ raise FileNotFoundError(f"Template not found: {self.template_path}")
26
+
27
+ with open(self.template_path, 'r') as f:
28
+ return yaml.safe_load(f)
29
+
30
+ def generate_profile(self, config: Dict[str, Any]) -> str:
31
+ """Generate an agent profile from configuration."""
32
+ # Set default values
33
+ config.setdefault('VERSION', '1.0.0')
34
+ config.setdefault('CREATED_DATE', datetime.now().strftime('%Y-%m-%d'))
35
+ config.setdefault('AUTHOR', 'claude-mpm')
36
+
37
+ # Convert template to string
38
+ template_str = yaml.dump(self.template, default_flow_style=False)
39
+
40
+ # Replace placeholders
41
+ result = self._replace_placeholders(template_str, config)
42
+
43
+ # Clean up any remaining placeholders
44
+ result = re.sub(r'\{\{[^}]+\}\}', '', result)
45
+
46
+ return result
47
+
48
+ def _replace_placeholders(self, template: str, values: Dict[str, Any]) -> str:
49
+ """Replace template placeholders with actual values."""
50
+ for key, value in values.items():
51
+ placeholder = f"{{{{{key}}}}}"
52
+ if isinstance(value, list):
53
+ # Format lists nicely
54
+ formatted_list = '\n'.join(f" - \"{item}\"" for item in value)
55
+ template = template.replace(placeholder, formatted_list)
56
+ else:
57
+ template = template.replace(placeholder, str(value))
58
+
59
+ return template
60
+
61
+ def generate_agent_documentation(self, agent_config: Dict[str, Any]) -> str:
62
+ """Generate markdown documentation for an agent."""
63
+ doc_lines = []
64
+
65
+ # Header
66
+ doc_lines.append(f"# {agent_config.get('name', 'Agent')} Documentation")
67
+ doc_lines.append("")
68
+
69
+ # Description
70
+ if 'description' in agent_config:
71
+ doc_lines.append("## Description")
72
+ doc_lines.append(agent_config['description'])
73
+ doc_lines.append("")
74
+
75
+ # Capabilities
76
+ if 'capabilities' in agent_config:
77
+ doc_lines.append("## Capabilities")
78
+ for capability in agent_config['capabilities']:
79
+ doc_lines.append(f"- {capability}")
80
+ doc_lines.append("")
81
+
82
+ # Tools
83
+ if 'tools' in agent_config:
84
+ doc_lines.append("## Required Tools")
85
+ for tool in agent_config['tools']:
86
+ if isinstance(tool, dict):
87
+ doc_lines.append(f"- **{tool['name']}**: {tool.get('description', '')}")
88
+ else:
89
+ doc_lines.append(f"- {tool}")
90
+ doc_lines.append("")
91
+
92
+ # Examples
93
+ if 'examples' in agent_config:
94
+ doc_lines.append("## Usage Examples")
95
+ for i, example in enumerate(agent_config['examples'], 1):
96
+ doc_lines.append(f"### Example {i}: {example.get('scenario', 'Scenario')}")
97
+ doc_lines.append("```")
98
+ doc_lines.append(f"Input: {example.get('input', '')}")
99
+ doc_lines.append("```")
100
+ doc_lines.append("Expected Output:")
101
+ doc_lines.append("```")
102
+ doc_lines.append(example.get('expected_output', ''))
103
+ doc_lines.append("```")
104
+ doc_lines.append("")
105
+
106
+ # Best Practices
107
+ if 'best_practices' in agent_config:
108
+ doc_lines.append("## Best Practices")
109
+ for practice in agent_config['best_practices']:
110
+ doc_lines.append(f"- {practice}")
111
+ doc_lines.append("")
112
+
113
+ return '\n'.join(doc_lines)
114
+
115
+ def create_agent_from_template(self, agent_name: str, role: str,
116
+ category: str = "analysis") -> Dict[str, Any]:
117
+ """Create a new agent configuration from template."""
118
+ return {
119
+ 'AGENT_NAME': agent_name,
120
+ 'AGENT_ID': agent_name.lower().replace(' ', '_'),
121
+ 'ROLE': role,
122
+ 'CATEGORY': category,
123
+ 'DESCRIPTION': f"Agent profile for {agent_name}",
124
+ 'AGENT_DESCRIPTION': f"Specialized {role} agent",
125
+ 'SPECIALIZATION': role.lower(),
126
+ 'CAPABILITIES_LIST': "- Analyze code structure\n- Identify patterns\n- Generate reports",
127
+ 'CONSTRAINTS_LIST': "- Read-only operations\n- Respect file permissions",
128
+ 'TOOL_NAME': 'code_analysis',
129
+ 'TOOL_DESCRIPTION': 'Analyzes code structure and patterns',
130
+ 'EXAMPLE_SCENARIO': 'Analyzing a Python project',
131
+ 'EXAMPLE_INPUT': 'Analyze the architecture of this codebase',
132
+ 'EXAMPLE_OUTPUT': 'Detailed analysis report with recommendations',
133
+ 'BEST_PRACTICE_1': 'Always validate input parameters',
134
+ 'BEST_PRACTICE_2': 'Provide clear, actionable insights',
135
+ 'BEST_PRACTICE_3': 'Include examples in reports',
136
+ 'ADDITIONAL_INSTRUCTIONS': 'Be thorough but concise in your analysis.'
137
+ }