claude-mpm 3.7.8__py3-none-any.whl → 3.9.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 (100) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_PM.md +0 -106
  3. claude_mpm/agents/INSTRUCTIONS.md +0 -96
  4. claude_mpm/agents/MEMORY.md +94 -0
  5. claude_mpm/agents/WORKFLOW.md +86 -0
  6. claude_mpm/agents/templates/code_analyzer.json +2 -2
  7. claude_mpm/agents/templates/data_engineer.json +1 -1
  8. claude_mpm/agents/templates/documentation.json +1 -1
  9. claude_mpm/agents/templates/engineer.json +1 -1
  10. claude_mpm/agents/templates/ops.json +1 -1
  11. claude_mpm/agents/templates/qa.json +1 -1
  12. claude_mpm/agents/templates/research.json +1 -1
  13. claude_mpm/agents/templates/security.json +1 -1
  14. claude_mpm/agents/templates/ticketing.json +3 -8
  15. claude_mpm/agents/templates/version_control.json +1 -1
  16. claude_mpm/agents/templates/web_qa.json +2 -2
  17. claude_mpm/agents/templates/web_ui.json +2 -2
  18. claude_mpm/cli/__init__.py +2 -2
  19. claude_mpm/cli/commands/__init__.py +2 -1
  20. claude_mpm/cli/commands/agents.py +8 -3
  21. claude_mpm/cli/commands/tickets.py +596 -19
  22. claude_mpm/cli/parser.py +217 -5
  23. claude_mpm/config/__init__.py +30 -39
  24. claude_mpm/config/socketio_config.py +8 -5
  25. claude_mpm/constants.py +13 -0
  26. claude_mpm/core/__init__.py +8 -18
  27. claude_mpm/core/cache.py +596 -0
  28. claude_mpm/core/claude_runner.py +166 -622
  29. claude_mpm/core/config.py +7 -3
  30. claude_mpm/core/constants.py +339 -0
  31. claude_mpm/core/container.py +548 -38
  32. claude_mpm/core/exceptions.py +392 -0
  33. claude_mpm/core/framework_loader.py +249 -93
  34. claude_mpm/core/interactive_session.py +479 -0
  35. claude_mpm/core/interfaces.py +424 -0
  36. claude_mpm/core/lazy.py +467 -0
  37. claude_mpm/core/logging_config.py +444 -0
  38. claude_mpm/core/oneshot_session.py +465 -0
  39. claude_mpm/core/optimized_agent_loader.py +485 -0
  40. claude_mpm/core/optimized_startup.py +490 -0
  41. claude_mpm/core/service_registry.py +52 -26
  42. claude_mpm/core/socketio_pool.py +162 -5
  43. claude_mpm/core/types.py +292 -0
  44. claude_mpm/core/typing_utils.py +477 -0
  45. claude_mpm/hooks/claude_hooks/hook_handler.py +213 -99
  46. claude_mpm/init.py +2 -1
  47. claude_mpm/services/__init__.py +78 -14
  48. claude_mpm/services/agent/__init__.py +24 -0
  49. claude_mpm/services/agent/deployment.py +2548 -0
  50. claude_mpm/services/agent/management.py +598 -0
  51. claude_mpm/services/agent/registry.py +813 -0
  52. claude_mpm/services/agents/deployment/agent_deployment.py +728 -308
  53. claude_mpm/services/agents/memory/agent_memory_manager.py +160 -4
  54. claude_mpm/services/async_session_logger.py +8 -3
  55. claude_mpm/services/communication/__init__.py +21 -0
  56. claude_mpm/services/communication/socketio.py +1933 -0
  57. claude_mpm/services/communication/websocket.py +479 -0
  58. claude_mpm/services/core/__init__.py +123 -0
  59. claude_mpm/services/core/base.py +247 -0
  60. claude_mpm/services/core/interfaces.py +951 -0
  61. claude_mpm/services/framework_claude_md_generator/__init__.py +10 -3
  62. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +14 -11
  63. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +23 -23
  64. claude_mpm/services/framework_claude_md_generator.py +3 -2
  65. claude_mpm/services/health_monitor.py +4 -3
  66. claude_mpm/services/hook_service.py +64 -4
  67. claude_mpm/services/infrastructure/__init__.py +21 -0
  68. claude_mpm/services/infrastructure/logging.py +202 -0
  69. claude_mpm/services/infrastructure/monitoring.py +893 -0
  70. claude_mpm/services/memory/indexed_memory.py +648 -0
  71. claude_mpm/services/project/__init__.py +21 -0
  72. claude_mpm/services/project/analyzer.py +864 -0
  73. claude_mpm/services/project/registry.py +608 -0
  74. claude_mpm/services/project_analyzer.py +95 -2
  75. claude_mpm/services/recovery_manager.py +15 -9
  76. claude_mpm/services/response_tracker.py +3 -5
  77. claude_mpm/services/socketio/__init__.py +25 -0
  78. claude_mpm/services/socketio/handlers/__init__.py +25 -0
  79. claude_mpm/services/socketio/handlers/base.py +121 -0
  80. claude_mpm/services/socketio/handlers/connection.py +198 -0
  81. claude_mpm/services/socketio/handlers/file.py +213 -0
  82. claude_mpm/services/socketio/handlers/git.py +723 -0
  83. claude_mpm/services/socketio/handlers/memory.py +27 -0
  84. claude_mpm/services/socketio/handlers/project.py +25 -0
  85. claude_mpm/services/socketio/handlers/registry.py +145 -0
  86. claude_mpm/services/socketio_client_manager.py +12 -7
  87. claude_mpm/services/socketio_server.py +156 -30
  88. claude_mpm/services/ticket_manager.py +172 -9
  89. claude_mpm/services/ticket_manager_di.py +1 -1
  90. claude_mpm/services/version_control/semantic_versioning.py +80 -7
  91. claude_mpm/services/version_control/version_parser.py +528 -0
  92. claude_mpm/utils/error_handler.py +1 -1
  93. claude_mpm/validation/agent_validator.py +27 -14
  94. claude_mpm/validation/frontmatter_validator.py +231 -0
  95. {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/METADATA +38 -128
  96. {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/RECORD +100 -59
  97. {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/WHEEL +0 -0
  98. {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/entry_points.txt +0 -0
  99. {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/licenses/LICENSE +0 -0
  100. {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/top_level.txt +0 -0
@@ -163,21 +163,77 @@ class FrameworkLoader:
163
163
  """
164
164
  Load INSTRUCTIONS.md or legacy CLAUDE.md from working directory.
165
165
 
166
+ NOTE: We no longer load CLAUDE.md since Claude Code already picks it up automatically.
167
+ This prevents duplication of instructions.
168
+
166
169
  Args:
167
170
  content: Dictionary to update with loaded instructions
168
171
  """
169
- working_instructions = Path.cwd() / "INSTRUCTIONS.md"
170
- working_claude = Path.cwd() / "CLAUDE.md" # Legacy support
172
+ # Disabled - Claude Code already reads CLAUDE.md automatically
173
+ # We don't need to duplicate it in the PM instructions
174
+ pass
175
+
176
+ def _load_workflow_instructions(self, content: Dict[str, Any]) -> None:
177
+ """
178
+ Load WORKFLOW.md with project-specific override support.
171
179
 
172
- if working_instructions.exists():
173
- loaded_content = self._try_load_file(working_instructions, "working directory INSTRUCTIONS.md")
180
+ Precedence:
181
+ 1. Project-specific: .claude-mpm/agents/WORKFLOW.md
182
+ 2. System default: src/claude_mpm/agents/WORKFLOW.md
183
+
184
+ Args:
185
+ content: Dictionary to update with workflow instructions
186
+ """
187
+ # Check for project-specific workflow first
188
+ project_workflow_path = Path.cwd() / ".claude-mpm" / "agents" / "WORKFLOW.md"
189
+ if project_workflow_path.exists():
190
+ loaded_content = self._try_load_file(project_workflow_path, "project-specific WORKFLOW.md")
174
191
  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)")
192
+ content["workflow_instructions"] = loaded_content
193
+ content["project_workflow"] = "project"
194
+ self.logger.info("Using project-specific WORKFLOW.md")
195
+ return
196
+
197
+ # Fall back to system workflow
198
+ if self.framework_path:
199
+ system_workflow_path = self.framework_path / "src" / "claude_mpm" / "agents" / "WORKFLOW.md"
200
+ if system_workflow_path.exists():
201
+ loaded_content = self._try_load_file(system_workflow_path, "system WORKFLOW.md")
202
+ if loaded_content:
203
+ content["workflow_instructions"] = loaded_content
204
+ content["project_workflow"] = "system"
205
+ self.logger.info("Using system WORKFLOW.md")
206
+
207
+ def _load_memory_instructions(self, content: Dict[str, Any]) -> None:
208
+ """
209
+ Load MEMORY.md with project-specific override support.
210
+
211
+ Precedence:
212
+ 1. Project-specific: .claude-mpm/agents/MEMORY.md
213
+ 2. System default: src/claude_mpm/agents/MEMORY.md
214
+
215
+ Args:
216
+ content: Dictionary to update with memory instructions
217
+ """
218
+ # Check for project-specific memory instructions first
219
+ project_memory_path = Path.cwd() / ".claude-mpm" / "agents" / "MEMORY.md"
220
+ if project_memory_path.exists():
221
+ loaded_content = self._try_load_file(project_memory_path, "project-specific MEMORY.md")
179
222
  if loaded_content:
180
- content["working_claude_md"] = loaded_content
223
+ content["memory_instructions"] = loaded_content
224
+ content["project_memory"] = "project"
225
+ self.logger.info("Using project-specific MEMORY.md")
226
+ return
227
+
228
+ # Fall back to system memory instructions
229
+ if self.framework_path:
230
+ system_memory_path = self.framework_path / "src" / "claude_mpm" / "agents" / "MEMORY.md"
231
+ if system_memory_path.exists():
232
+ loaded_content = self._try_load_file(system_memory_path, "system MEMORY.md")
233
+ if loaded_content:
234
+ content["memory_instructions"] = loaded_content
235
+ content["project_memory"] = "system"
236
+ self.logger.info("Using system MEMORY.md")
181
237
 
182
238
  def _load_single_agent(self, agent_file: Path) -> tuple[Optional[str], Optional[str]]:
183
239
  """
@@ -250,7 +306,11 @@ class FrameworkLoader:
250
306
  "version": "unknown",
251
307
  "loaded": False,
252
308
  "working_claude_md": "",
253
- "framework_instructions": ""
309
+ "framework_instructions": "",
310
+ "workflow_instructions": "",
311
+ "project_workflow": "",
312
+ "memory_instructions": "",
313
+ "project_memory": ""
254
314
  }
255
315
 
256
316
  # Load instructions file from working directory
@@ -281,6 +341,12 @@ class FrameworkLoader:
281
341
  if base_pm_content:
282
342
  content["base_pm_instructions"] = base_pm_content
283
343
 
344
+ # Load WORKFLOW.md - check for project-specific first, then system
345
+ self._load_workflow_instructions(content)
346
+
347
+ # Load MEMORY.md - check for project-specific first, then system
348
+ self._load_memory_instructions(content)
349
+
284
350
  # Discover agent directories
285
351
  agents_dir, templates_dir, main_dir = self._discover_framework_paths()
286
352
 
@@ -296,24 +362,49 @@ class FrameworkLoader:
296
362
  Returns:
297
363
  Complete framework instructions ready for injection
298
364
  """
299
- if self.framework_content["loaded"] or self.framework_content["working_claude_md"]:
365
+ if self.framework_content["loaded"]:
300
366
  # Build framework from components
301
367
  return self._format_full_framework()
302
368
  else:
303
369
  # Use minimal fallback
304
370
  return self._format_minimal_framework()
305
371
 
372
+ def _strip_metadata_comments(self, content: str) -> str:
373
+ """Strip metadata HTML comments from content.
374
+
375
+ Removes comments like:
376
+ <!-- FRAMEWORK_VERSION: 0010 -->
377
+ <!-- LAST_MODIFIED: 2025-08-10T00:00:00Z -->
378
+ """
379
+ import re
380
+ # Remove HTML comments that contain metadata
381
+ cleaned = re.sub(r'<!--\s*(FRAMEWORK_VERSION|LAST_MODIFIED|WORKFLOW_VERSION|PROJECT_WORKFLOW_VERSION|CUSTOM_PROJECT_WORKFLOW)[^>]*-->\n?', '', content)
382
+ # Also remove any leading blank lines that might result
383
+ cleaned = cleaned.lstrip('\n')
384
+ return cleaned
385
+
306
386
  def _format_full_framework(self) -> str:
307
387
  """Format full framework instructions."""
308
388
  from datetime import datetime
309
389
 
310
390
  # If we have the full framework INSTRUCTIONS.md, use it
311
391
  if self.framework_content.get("framework_instructions"):
312
- instructions = self.framework_content["framework_instructions"]
392
+ instructions = self._strip_metadata_comments(self.framework_content["framework_instructions"])
393
+
394
+ # Note: We don't add working directory CLAUDE.md here since Claude Code
395
+ # already picks it up automatically. This prevents duplication.
313
396
 
314
- # Add working directory instructions if they exist
315
- if self.framework_content["working_claude_md"]:
316
- instructions += f"\n\n## Working Directory Instructions\n{self.framework_content['working_claude_md']}\n"
397
+ # Add WORKFLOW.md after instructions
398
+ if self.framework_content.get("workflow_instructions"):
399
+ workflow_content = self._strip_metadata_comments(self.framework_content['workflow_instructions'])
400
+ instructions += f"\n\n{workflow_content}\n"
401
+ # Note: project-specific workflow is being used (logged elsewhere)
402
+
403
+ # Add MEMORY.md after workflow instructions
404
+ if self.framework_content.get("memory_instructions"):
405
+ memory_content = self._strip_metadata_comments(self.framework_content['memory_instructions'])
406
+ instructions += f"\n\n{memory_content}\n"
407
+ # Note: project-specific memory instructions being used (logged elsewhere)
317
408
 
318
409
  # Add dynamic agent capabilities section
319
410
  instructions += self._generate_agent_capabilities_section()
@@ -324,17 +415,16 @@ class FrameworkLoader:
324
415
 
325
416
  # Add BASE_PM.md framework requirements AFTER INSTRUCTIONS.md
326
417
  if self.framework_content.get("base_pm_instructions"):
327
- instructions += f"\n\n{self.framework_content['base_pm_instructions']}\n"
418
+ base_pm = self._strip_metadata_comments(self.framework_content['base_pm_instructions'])
419
+ instructions += f"\n\n{base_pm}"
420
+
421
+ # Clean up any trailing whitespace
422
+ instructions = instructions.rstrip() + "\n"
328
423
 
329
424
  return instructions
330
425
 
331
426
  # Otherwise fall back to generating framework
332
- instructions = f"""
333
- <!-- Framework injected by Claude MPM -->
334
- <!-- Version: {self.framework_content['version']} -->
335
- <!-- Timestamp: {datetime.now().isoformat()} -->
336
-
337
- # Claude MPM Framework Instructions
427
+ instructions = """# Claude MPM Framework Instructions
338
428
 
339
429
  You are operating within the Claude Multi-Agent Project Manager (MPM) framework.
340
430
 
@@ -348,13 +438,8 @@ You are a multi-agent orchestrator. Your primary responsibilities are:
348
438
 
349
439
  """
350
440
 
351
- # Add working directory INSTRUCTIONS.md (or CLAUDE.md) if exists
352
- if self.framework_content["working_claude_md"]:
353
- instructions += f"""
354
- ## Working Directory Instructions
355
- {self.framework_content["working_claude_md"]}
356
-
357
- """
441
+ # Note: We don't add working directory CLAUDE.md here since Claude Code
442
+ # already picks it up automatically. This prevents duplication.
358
443
 
359
444
  # Add agent definitions
360
445
  if self.framework_content["agents"]:
@@ -440,7 +525,6 @@ Extract tickets from these patterns:
440
525
  import yaml
441
526
 
442
527
  # Read directly from deployed agents in .claude/agents/
443
- # This ensures we show the exact agent IDs that work with the Task tool
444
528
  agents_dir = Path.cwd() / ".claude" / "agents"
445
529
 
446
530
  if not agents_dir.exists():
@@ -449,77 +533,57 @@ Extract tickets from these patterns:
449
533
 
450
534
  # Build capabilities section
451
535
  section = "\n\n## Available Agent Capabilities\n\n"
452
- section += "You have the following specialized agents available for delegation:\n\n"
453
536
 
454
537
  # Collect deployed agents
455
538
  deployed_agents = []
456
539
  for agent_file in agents_dir.glob("*.md"):
457
- # Skip hidden files and system files
458
540
  if agent_file.name.startswith('.'):
459
541
  continue
460
542
 
461
- # The agent ID is the filename without extension
462
- # This is what the Task tool expects
463
- agent_id = agent_file.stem
464
-
465
- # Try to read agent metadata from frontmatter
466
- agent_name = agent_id.replace('_', ' ').title()
467
- agent_desc = "Specialized agent"
468
-
469
- try:
470
- with open(agent_file, 'r') as f:
471
- content = f.read()
472
- # Extract YAML frontmatter if present
473
- if content.startswith('---'):
474
- end_marker = content.find('---', 3)
475
- if end_marker > 0:
476
- frontmatter = content[3:end_marker]
477
- metadata = yaml.safe_load(frontmatter)
478
- if metadata:
479
- agent_name = metadata.get('name', agent_name)
480
- agent_desc = metadata.get('description', agent_desc)
481
- except Exception as e:
482
- self.logger.debug(f"Could not read metadata from {agent_file}: {e}")
483
-
484
- deployed_agents.append((agent_id, agent_name, agent_desc))
543
+ # Parse agent metadata
544
+ agent_data = self._parse_agent_metadata(agent_file)
545
+ if agent_data:
546
+ deployed_agents.append(agent_data)
485
547
 
486
548
  if not deployed_agents:
487
549
  return self._get_fallback_capabilities()
488
550
 
489
- # Sort agents and display them
490
- deployed_agents.sort(key=lambda x: x[0])
551
+ # Sort agents alphabetically by ID
552
+ deployed_agents.sort(key=lambda x: x['id'])
491
553
 
492
- # Group common agent types
493
- core_agents = []
494
- other_agents = []
554
+ # Display all agents with their rich descriptions
555
+ for agent in deployed_agents:
556
+ # Clean up display name - handle common acronyms
557
+ display_name = agent['display_name']
558
+ display_name = display_name.replace('Qa ', 'QA ').replace('Ui ', 'UI ').replace('Api ', 'API ')
559
+ if display_name.lower() == 'qa agent':
560
+ display_name = 'QA Agent'
561
+
562
+ section += f"\n### {display_name} (`{agent['id']}`)\n"
563
+ section += f"{agent['description']}\n"
564
+
565
+ # Add any additional metadata if present
566
+ if agent.get('authority'):
567
+ section += f"- **Authority**: {agent['authority']}\n"
568
+ if agent.get('primary_function'):
569
+ section += f"- **Primary Function**: {agent['primary_function']}\n"
570
+ if agent.get('handoff_to'):
571
+ section += f"- **Handoff To**: {agent['handoff_to']}\n"
572
+ if agent.get('tools') and agent['tools'] != 'standard':
573
+ section += f"- **Tools**: {agent['tools']}\n"
574
+ if agent.get('model') and agent['model'] != 'opus':
575
+ section += f"- **Model**: {agent['model']}\n"
495
576
 
496
- core_types = ['engineer', 'research', 'qa', 'documentation', 'security',
497
- 'data_engineer', 'ops', 'version_control']
577
+ # Add simple Context-Aware Agent Selection
578
+ section += "\n## Context-Aware Agent Selection\n\n"
579
+ section += "Select agents based on their descriptions above. Key principles:\n"
580
+ section += "- **PM questions** → Answer directly (only exception)\n"
581
+ section += "- Match task requirements to agent descriptions and authority\n"
582
+ section += "- Consider agent handoff recommendations\n"
583
+ section += "- Use the agent ID in parentheses when delegating via Task tool\n"
498
584
 
499
- for agent_id, name, desc in deployed_agents:
500
- if agent_id in core_types:
501
- core_agents.append((agent_id, name, desc))
502
- else:
503
- other_agents.append((agent_id, name, desc))
504
-
505
- # Display core agents first
506
- if core_agents:
507
- section += "### Engineering Agents\n"
508
- for agent_id, name, desc in core_agents:
509
- # Format: Name (agent_id) - use Name for TodoWrite, agent_id for Task tool
510
- clean_name = name.replace(' Agent', '').replace('-', ' ')
511
- section += f"- **{clean_name}** (`{agent_id}`): {desc}\n"
512
-
513
- # Display other/custom agents
514
- if other_agents:
515
- section += "\n### Research Agents\n"
516
- for agent_id, name, desc in other_agents:
517
- clean_name = name.replace(' Agent', '').replace('-', ' ')
518
- section += f"- **{clean_name}** (`{agent_id}`): {desc}\n"
519
-
520
- # Add summary and usage instructions
585
+ # Add summary
521
586
  section += f"\n**Total Available Agents**: {len(deployed_agents)}\n"
522
- section += "Use the agent ID in parentheses when delegating tasks via the Task tool.\n"
523
587
 
524
588
  return section
525
589
 
@@ -527,6 +591,98 @@ Extract tickets from these patterns:
527
591
  self.logger.warning(f"Could not generate dynamic agent capabilities: {e}")
528
592
  return self._get_fallback_capabilities()
529
593
 
594
+ def _parse_agent_metadata(self, agent_file: Path) -> Optional[Dict[str, Any]]:
595
+ """Parse agent metadata from deployed agent file.
596
+
597
+ Returns:
598
+ Dictionary with agent metadata directly from YAML frontmatter.
599
+ """
600
+ try:
601
+ import yaml
602
+
603
+ with open(agent_file, 'r') as f:
604
+ content = f.read()
605
+
606
+ # Default values
607
+ agent_data = {
608
+ 'id': agent_file.stem,
609
+ 'display_name': agent_file.stem.replace('_', ' ').replace('-', ' ').title(),
610
+ 'description': 'Specialized agent'
611
+ }
612
+
613
+ # Extract YAML frontmatter if present
614
+ if content.startswith('---'):
615
+ end_marker = content.find('---', 3)
616
+ if end_marker > 0:
617
+ frontmatter = content[3:end_marker]
618
+ metadata = yaml.safe_load(frontmatter)
619
+ if metadata:
620
+ # Use name as ID for Task tool
621
+ agent_data['id'] = metadata.get('name', agent_data['id'])
622
+ agent_data['display_name'] = metadata.get('name', agent_data['display_name']).replace('-', ' ').title()
623
+
624
+ # Copy all metadata fields directly
625
+ for key, value in metadata.items():
626
+ if key not in ['name']: # Skip already processed fields
627
+ agent_data[key] = value
628
+
629
+ # IMPORTANT: Do NOT add spaces to tools field - it breaks deployment!
630
+ # Tools must remain as comma-separated without spaces: "Read,Write,Edit"
631
+
632
+ return agent_data
633
+
634
+ except Exception as e:
635
+ self.logger.debug(f"Could not parse metadata from {agent_file}: {e}")
636
+ return None
637
+
638
+ def _generate_agent_selection_guide(self, deployed_agents: list) -> str:
639
+ """Generate Context-Aware Agent Selection guide from deployed agents.
640
+
641
+ Creates a mapping of task types to appropriate agents based on their
642
+ descriptions and capabilities.
643
+ """
644
+ guide = ""
645
+
646
+ # Build selection mapping based on deployed agents
647
+ selection_map = {}
648
+
649
+ for agent in deployed_agents:
650
+ agent_id = agent['id']
651
+ desc_lower = agent['description'].lower()
652
+
653
+ # Map task types to agents based on their descriptions
654
+ if 'implementation' in desc_lower or ('engineer' in agent_id and 'data' not in agent_id):
655
+ selection_map['Implementation tasks'] = f"{agent['display_name']} (`{agent_id}`)"
656
+ if 'codebase analysis' in desc_lower or 'research' in agent_id:
657
+ selection_map['Codebase analysis'] = f"{agent['display_name']} (`{agent_id}`)"
658
+ if 'testing' in desc_lower or 'qa' in agent_id:
659
+ selection_map['Testing/quality'] = f"{agent['display_name']} (`{agent_id}`)"
660
+ if 'documentation' in desc_lower:
661
+ selection_map['Documentation'] = f"{agent['display_name']} (`{agent_id}`)"
662
+ if 'security' in desc_lower or 'sast' in desc_lower:
663
+ selection_map['Security operations'] = f"{agent['display_name']} (`{agent_id}`)"
664
+ if 'deployment' in desc_lower or 'infrastructure' in desc_lower or 'ops' in agent_id:
665
+ selection_map['Deployment/infrastructure'] = f"{agent['display_name']} (`{agent_id}`)"
666
+ if 'data' in desc_lower and ('pipeline' in desc_lower or 'etl' in desc_lower):
667
+ selection_map['Data pipeline/ETL'] = f"{agent['display_name']} (`{agent_id}`)"
668
+ if 'git' in desc_lower or 'version control' in desc_lower:
669
+ selection_map['Version control'] = f"{agent['display_name']} (`{agent_id}`)"
670
+ if 'ticket' in desc_lower or 'epic' in desc_lower:
671
+ selection_map['Ticket/issue management'] = f"{agent['display_name']} (`{agent_id}`)"
672
+ if 'browser' in desc_lower or 'e2e' in desc_lower:
673
+ selection_map['Browser/E2E testing'] = f"{agent['display_name']} (`{agent_id}`)"
674
+ if 'frontend' in desc_lower or 'ui' in desc_lower or 'html' in desc_lower:
675
+ selection_map['Frontend/UI development'] = f"{agent['display_name']} (`{agent_id}`)"
676
+
677
+ # Always include PM questions
678
+ selection_map['PM questions'] = "Answer directly (only exception)"
679
+
680
+ # Format the selection guide
681
+ for task_type, agent_info in selection_map.items():
682
+ guide += f"- **{task_type}** → {agent_info}\n"
683
+
684
+ return guide
685
+
530
686
  def _get_fallback_capabilities(self) -> str:
531
687
  """Return fallback capabilities when dynamic discovery fails."""
532
688
  return """
@@ -536,13 +692,13 @@ Extract tickets from these patterns:
536
692
  You have the following specialized agents available for delegation:
537
693
 
538
694
  - **Engineer** (`engineer`): Code implementation and development
539
- - **Research** (`research`): Investigation and analysis
540
- - **QA** (`qa`): Testing and quality assurance
541
- - **Documentation** (`documentation`): Documentation creation and maintenance
542
- - **Security** (`security`): Security analysis and protection
543
- - **Data Engineer** (`data_engineer`): Data management and pipelines
544
- - **Ops** (`ops`): Deployment and operations
545
- - **Version Control** (`version_control`): Git operations and version management
695
+ - **Research** (`research-agent`): Investigation and analysis
696
+ - **QA** (`qa-agent`): Testing and quality assurance
697
+ - **Documentation** (`documentation-agent`): Documentation creation and maintenance
698
+ - **Security** (`security-agent`): Security analysis and protection
699
+ - **Data Engineer** (`data-engineer`): Data management and pipelines
700
+ - **Ops** (`ops-agent`): Deployment and operations
701
+ - **Version Control** (`version-control`): Git operations and version management
546
702
 
547
703
  **IMPORTANT**: Use the exact agent ID in parentheses when delegating tasks.
548
704
  """