claude-mpm 3.7.4__py3-none-any.whl → 3.8.1__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 (117) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_PM.md +0 -106
  3. claude_mpm/agents/INSTRUCTIONS.md +0 -78
  4. claude_mpm/agents/MEMORY.md +88 -0
  5. claude_mpm/agents/WORKFLOW.md +86 -0
  6. claude_mpm/agents/schema/agent_schema.json +1 -1
  7. claude_mpm/agents/templates/code_analyzer.json +26 -11
  8. claude_mpm/agents/templates/data_engineer.json +4 -7
  9. claude_mpm/agents/templates/documentation.json +2 -2
  10. claude_mpm/agents/templates/engineer.json +2 -2
  11. claude_mpm/agents/templates/ops.json +3 -8
  12. claude_mpm/agents/templates/qa.json +2 -3
  13. claude_mpm/agents/templates/research.json +2 -3
  14. claude_mpm/agents/templates/security.json +3 -6
  15. claude_mpm/agents/templates/ticketing.json +4 -9
  16. claude_mpm/agents/templates/version_control.json +3 -3
  17. claude_mpm/agents/templates/web_qa.json +4 -4
  18. claude_mpm/agents/templates/web_ui.json +4 -4
  19. claude_mpm/cli/__init__.py +2 -2
  20. claude_mpm/cli/commands/__init__.py +2 -1
  21. claude_mpm/cli/commands/agents.py +118 -1
  22. claude_mpm/cli/commands/tickets.py +596 -19
  23. claude_mpm/cli/parser.py +228 -5
  24. claude_mpm/config/__init__.py +30 -39
  25. claude_mpm/config/socketio_config.py +8 -5
  26. claude_mpm/constants.py +13 -0
  27. claude_mpm/core/__init__.py +8 -18
  28. claude_mpm/core/cache.py +596 -0
  29. claude_mpm/core/claude_runner.py +166 -622
  30. claude_mpm/core/config.py +5 -1
  31. claude_mpm/core/constants.py +339 -0
  32. claude_mpm/core/container.py +461 -22
  33. claude_mpm/core/exceptions.py +392 -0
  34. claude_mpm/core/framework_loader.py +208 -93
  35. claude_mpm/core/interactive_session.py +432 -0
  36. claude_mpm/core/interfaces.py +424 -0
  37. claude_mpm/core/lazy.py +467 -0
  38. claude_mpm/core/logging_config.py +444 -0
  39. claude_mpm/core/oneshot_session.py +465 -0
  40. claude_mpm/core/optimized_agent_loader.py +485 -0
  41. claude_mpm/core/optimized_startup.py +490 -0
  42. claude_mpm/core/service_registry.py +52 -26
  43. claude_mpm/core/socketio_pool.py +162 -5
  44. claude_mpm/core/types.py +292 -0
  45. claude_mpm/core/typing_utils.py +477 -0
  46. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +46 -2
  47. claude_mpm/dashboard/templates/index.html +5 -5
  48. claude_mpm/hooks/claude_hooks/hook_handler.py +213 -99
  49. claude_mpm/init.py +2 -1
  50. claude_mpm/services/__init__.py +78 -14
  51. claude_mpm/services/agent/__init__.py +24 -0
  52. claude_mpm/services/agent/deployment.py +2548 -0
  53. claude_mpm/services/agent/management.py +598 -0
  54. claude_mpm/services/agent/registry.py +813 -0
  55. claude_mpm/services/agents/deployment/agent_deployment.py +592 -269
  56. claude_mpm/services/agents/deployment/async_agent_deployment.py +5 -1
  57. claude_mpm/services/agents/management/agent_capabilities_generator.py +21 -11
  58. claude_mpm/services/agents/memory/agent_memory_manager.py +156 -1
  59. claude_mpm/services/async_session_logger.py +8 -3
  60. claude_mpm/services/communication/__init__.py +21 -0
  61. claude_mpm/services/communication/socketio.py +1933 -0
  62. claude_mpm/services/communication/websocket.py +479 -0
  63. claude_mpm/services/core/__init__.py +123 -0
  64. claude_mpm/services/core/base.py +247 -0
  65. claude_mpm/services/core/interfaces.py +951 -0
  66. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +23 -23
  67. claude_mpm/services/framework_claude_md_generator.py +3 -2
  68. claude_mpm/services/health_monitor.py +4 -3
  69. claude_mpm/services/hook_service.py +64 -4
  70. claude_mpm/services/infrastructure/__init__.py +21 -0
  71. claude_mpm/services/infrastructure/logging.py +202 -0
  72. claude_mpm/services/infrastructure/monitoring.py +893 -0
  73. claude_mpm/services/memory/indexed_memory.py +648 -0
  74. claude_mpm/services/project/__init__.py +21 -0
  75. claude_mpm/services/project/analyzer.py +864 -0
  76. claude_mpm/services/project/registry.py +608 -0
  77. claude_mpm/services/project_analyzer.py +95 -2
  78. claude_mpm/services/recovery_manager.py +15 -9
  79. claude_mpm/services/socketio/__init__.py +25 -0
  80. claude_mpm/services/socketio/handlers/__init__.py +25 -0
  81. claude_mpm/services/socketio/handlers/base.py +121 -0
  82. claude_mpm/services/socketio/handlers/connection.py +198 -0
  83. claude_mpm/services/socketio/handlers/file.py +213 -0
  84. claude_mpm/services/socketio/handlers/git.py +723 -0
  85. claude_mpm/services/socketio/handlers/memory.py +27 -0
  86. claude_mpm/services/socketio/handlers/project.py +25 -0
  87. claude_mpm/services/socketio/handlers/registry.py +145 -0
  88. claude_mpm/services/socketio_client_manager.py +12 -7
  89. claude_mpm/services/socketio_server.py +156 -30
  90. claude_mpm/services/ticket_manager.py +377 -51
  91. claude_mpm/utils/agent_dependency_loader.py +66 -15
  92. claude_mpm/utils/error_handler.py +1 -1
  93. claude_mpm/utils/robust_installer.py +587 -0
  94. claude_mpm/validation/agent_validator.py +27 -14
  95. claude_mpm/validation/frontmatter_validator.py +231 -0
  96. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/METADATA +74 -41
  97. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/RECORD +101 -76
  98. claude_mpm/.claude-mpm/logs/hooks_20250728.log +0 -10
  99. claude_mpm/agents/agent-template.yaml +0 -83
  100. claude_mpm/cli/README.md +0 -108
  101. claude_mpm/cli_module/refactoring_guide.md +0 -253
  102. claude_mpm/config/async_logging_config.yaml +0 -145
  103. claude_mpm/core/.claude-mpm/logs/hooks_20250730.log +0 -34
  104. claude_mpm/dashboard/.claude-mpm/memories/README.md +0 -36
  105. claude_mpm/dashboard/README.md +0 -121
  106. claude_mpm/dashboard/static/js/dashboard.js.backup +0 -1973
  107. claude_mpm/dashboard/templates/.claude-mpm/memories/README.md +0 -36
  108. claude_mpm/dashboard/templates/.claude-mpm/memories/engineer_agent.md +0 -39
  109. claude_mpm/dashboard/templates/.claude-mpm/memories/version_control_agent.md +0 -38
  110. claude_mpm/hooks/README.md +0 -96
  111. claude_mpm/schemas/agent_schema.json +0 -435
  112. claude_mpm/services/framework_claude_md_generator/README.md +0 -92
  113. claude_mpm/services/version_control/VERSION +0 -1
  114. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/WHEEL +0 -0
  115. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/entry_points.txt +0 -0
  116. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/licenses/LICENSE +0 -0
  117. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/top_level.txt +0 -0
@@ -163,21 +163,46 @@ 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")
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)")
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")
179
191
  if loaded_content:
180
- content["working_claude_md"] = loaded_content
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")
181
206
 
182
207
  def _load_single_agent(self, agent_file: Path) -> tuple[Optional[str], Optional[str]]:
183
208
  """
@@ -250,7 +275,9 @@ class FrameworkLoader:
250
275
  "version": "unknown",
251
276
  "loaded": False,
252
277
  "working_claude_md": "",
253
- "framework_instructions": ""
278
+ "framework_instructions": "",
279
+ "workflow_instructions": "",
280
+ "project_workflow": ""
254
281
  }
255
282
 
256
283
  # Load instructions file from working directory
@@ -281,6 +308,9 @@ class FrameworkLoader:
281
308
  if base_pm_content:
282
309
  content["base_pm_instructions"] = base_pm_content
283
310
 
311
+ # Load WORKFLOW.md - check for project-specific first, then system
312
+ self._load_workflow_instructions(content)
313
+
284
314
  # Discover agent directories
285
315
  agents_dir, templates_dir, main_dir = self._discover_framework_paths()
286
316
 
@@ -296,24 +326,43 @@ class FrameworkLoader:
296
326
  Returns:
297
327
  Complete framework instructions ready for injection
298
328
  """
299
- if self.framework_content["loaded"] or self.framework_content["working_claude_md"]:
329
+ if self.framework_content["loaded"]:
300
330
  # Build framework from components
301
331
  return self._format_full_framework()
302
332
  else:
303
333
  # Use minimal fallback
304
334
  return self._format_minimal_framework()
305
335
 
336
+ def _strip_metadata_comments(self, content: str) -> str:
337
+ """Strip metadata HTML comments from content.
338
+
339
+ Removes comments like:
340
+ <!-- FRAMEWORK_VERSION: 0010 -->
341
+ <!-- LAST_MODIFIED: 2025-08-10T00:00:00Z -->
342
+ """
343
+ import re
344
+ # Remove HTML comments that contain metadata
345
+ cleaned = re.sub(r'<!--\s*(FRAMEWORK_VERSION|LAST_MODIFIED|WORKFLOW_VERSION|PROJECT_WORKFLOW_VERSION|CUSTOM_PROJECT_WORKFLOW)[^>]*-->\n?', '', content)
346
+ # Also remove any leading blank lines that might result
347
+ cleaned = cleaned.lstrip('\n')
348
+ return cleaned
349
+
306
350
  def _format_full_framework(self) -> str:
307
351
  """Format full framework instructions."""
308
352
  from datetime import datetime
309
353
 
310
354
  # If we have the full framework INSTRUCTIONS.md, use it
311
355
  if self.framework_content.get("framework_instructions"):
312
- instructions = self.framework_content["framework_instructions"]
356
+ instructions = self._strip_metadata_comments(self.framework_content["framework_instructions"])
313
357
 
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"
358
+ # Note: We don't add working directory CLAUDE.md here since Claude Code
359
+ # already picks it up automatically. This prevents duplication.
360
+
361
+ # Add WORKFLOW.md after instructions
362
+ if self.framework_content.get("workflow_instructions"):
363
+ workflow_content = self._strip_metadata_comments(self.framework_content['workflow_instructions'])
364
+ instructions += f"\n\n{workflow_content}\n"
365
+ # Note: project-specific workflow is being used (logged elsewhere)
317
366
 
318
367
  # Add dynamic agent capabilities section
319
368
  instructions += self._generate_agent_capabilities_section()
@@ -324,17 +373,16 @@ class FrameworkLoader:
324
373
 
325
374
  # Add BASE_PM.md framework requirements AFTER INSTRUCTIONS.md
326
375
  if self.framework_content.get("base_pm_instructions"):
327
- instructions += f"\n\n{self.framework_content['base_pm_instructions']}\n"
376
+ base_pm = self._strip_metadata_comments(self.framework_content['base_pm_instructions'])
377
+ instructions += f"\n\n{base_pm}"
378
+
379
+ # Clean up any trailing whitespace
380
+ instructions = instructions.rstrip() + "\n"
328
381
 
329
382
  return instructions
330
383
 
331
384
  # 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
385
+ instructions = """# Claude MPM Framework Instructions
338
386
 
339
387
  You are operating within the Claude Multi-Agent Project Manager (MPM) framework.
340
388
 
@@ -348,13 +396,8 @@ You are a multi-agent orchestrator. Your primary responsibilities are:
348
396
 
349
397
  """
350
398
 
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
- """
399
+ # Note: We don't add working directory CLAUDE.md here since Claude Code
400
+ # already picks it up automatically. This prevents duplication.
358
401
 
359
402
  # Add agent definitions
360
403
  if self.framework_content["agents"]:
@@ -440,7 +483,6 @@ Extract tickets from these patterns:
440
483
  import yaml
441
484
 
442
485
  # Read directly from deployed agents in .claude/agents/
443
- # This ensures we show the exact agent IDs that work with the Task tool
444
486
  agents_dir = Path.cwd() / ".claude" / "agents"
445
487
 
446
488
  if not agents_dir.exists():
@@ -449,76 +491,57 @@ Extract tickets from these patterns:
449
491
 
450
492
  # Build capabilities section
451
493
  section = "\n\n## Available Agent Capabilities\n\n"
452
- section += "You have the following specialized agents available for delegation:\n\n"
453
494
 
454
495
  # Collect deployed agents
455
496
  deployed_agents = []
456
497
  for agent_file in agents_dir.glob("*.md"):
457
- # Skip hidden files and system files
458
498
  if agent_file.name.startswith('.'):
459
499
  continue
460
500
 
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))
501
+ # Parse agent metadata
502
+ agent_data = self._parse_agent_metadata(agent_file)
503
+ if agent_data:
504
+ deployed_agents.append(agent_data)
485
505
 
486
506
  if not deployed_agents:
487
507
  return self._get_fallback_capabilities()
488
508
 
489
- # Sort agents and display them
490
- deployed_agents.sort(key=lambda x: x[0])
491
-
492
- # Group common agent types
493
- core_agents = []
494
- other_agents = []
495
-
496
- core_types = ['engineer', 'research', 'qa', 'documentation', 'security',
497
- 'data_engineer', 'ops', 'version_control']
498
-
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))
509
+ # Sort agents alphabetically by ID
510
+ deployed_agents.sort(key=lambda x: x['id'])
504
511
 
505
- # Display core agents first
506
- if core_agents:
507
- section += "### Core Agents\n"
508
- for agent_id, name, desc in core_agents:
509
- section += f"- **{name}** (`{agent_id}`): {desc}\n"
512
+ # Display all agents with their rich descriptions
513
+ for agent in deployed_agents:
514
+ # Clean up display name - handle common acronyms
515
+ display_name = agent['display_name']
516
+ display_name = display_name.replace('Qa ', 'QA ').replace('Ui ', 'UI ').replace('Api ', 'API ')
517
+ if display_name.lower() == 'qa agent':
518
+ display_name = 'QA Agent'
519
+
520
+ section += f"\n### {display_name} (`{agent['id']}`)\n"
521
+ section += f"{agent['description']}\n"
522
+
523
+ # Add any additional metadata if present
524
+ if agent.get('authority'):
525
+ section += f"- **Authority**: {agent['authority']}\n"
526
+ if agent.get('primary_function'):
527
+ section += f"- **Primary Function**: {agent['primary_function']}\n"
528
+ if agent.get('handoff_to'):
529
+ section += f"- **Handoff To**: {agent['handoff_to']}\n"
530
+ if agent.get('tools') and agent['tools'] != 'standard':
531
+ section += f"- **Tools**: {agent['tools']}\n"
532
+ if agent.get('model') and agent['model'] != 'opus':
533
+ section += f"- **Model**: {agent['model']}\n"
510
534
 
511
- # Display other/custom agents
512
- if other_agents:
513
- section += "\n### Custom/Project Agents\n"
514
- for agent_id, name, desc in other_agents:
515
- section += f"- **{name}** (`{agent_id}`): {desc}\n"
535
+ # Add simple Context-Aware Agent Selection
536
+ section += "\n## Context-Aware Agent Selection\n\n"
537
+ section += "Select agents based on their descriptions above. Key principles:\n"
538
+ section += "- **PM questions** → Answer directly (only exception)\n"
539
+ section += "- Match task requirements to agent descriptions and authority\n"
540
+ section += "- Consider agent handoff recommendations\n"
541
+ section += "- Use the agent ID in parentheses when delegating via Task tool\n"
516
542
 
517
- # Add summary and usage instructions
543
+ # Add summary
518
544
  section += f"\n**Total Available Agents**: {len(deployed_agents)}\n"
519
- section += "\n**IMPORTANT**: Use the exact agent ID shown in parentheses when delegating tasks.\n"
520
- section += "For example: `**research**: Analyze the codebase architecture`\n"
521
- section += "NOT: `**research_agent**: ...` or `**Research Agent**: ...`\n"
522
545
 
523
546
  return section
524
547
 
@@ -526,6 +549,98 @@ Extract tickets from these patterns:
526
549
  self.logger.warning(f"Could not generate dynamic agent capabilities: {e}")
527
550
  return self._get_fallback_capabilities()
528
551
 
552
+ def _parse_agent_metadata(self, agent_file: Path) -> Optional[Dict[str, Any]]:
553
+ """Parse agent metadata from deployed agent file.
554
+
555
+ Returns:
556
+ Dictionary with agent metadata directly from YAML frontmatter.
557
+ """
558
+ try:
559
+ import yaml
560
+
561
+ with open(agent_file, 'r') as f:
562
+ content = f.read()
563
+
564
+ # Default values
565
+ agent_data = {
566
+ 'id': agent_file.stem,
567
+ 'display_name': agent_file.stem.replace('_', ' ').replace('-', ' ').title(),
568
+ 'description': 'Specialized agent'
569
+ }
570
+
571
+ # Extract YAML frontmatter if present
572
+ if content.startswith('---'):
573
+ end_marker = content.find('---', 3)
574
+ if end_marker > 0:
575
+ frontmatter = content[3:end_marker]
576
+ metadata = yaml.safe_load(frontmatter)
577
+ if metadata:
578
+ # Use name as ID for Task tool
579
+ agent_data['id'] = metadata.get('name', agent_data['id'])
580
+ agent_data['display_name'] = metadata.get('name', agent_data['display_name']).replace('-', ' ').title()
581
+
582
+ # Copy all metadata fields directly
583
+ for key, value in metadata.items():
584
+ if key not in ['name']: # Skip already processed fields
585
+ agent_data[key] = value
586
+
587
+ # IMPORTANT: Do NOT add spaces to tools field - it breaks deployment!
588
+ # Tools must remain as comma-separated without spaces: "Read,Write,Edit"
589
+
590
+ return agent_data
591
+
592
+ except Exception as e:
593
+ self.logger.debug(f"Could not parse metadata from {agent_file}: {e}")
594
+ return None
595
+
596
+ def _generate_agent_selection_guide(self, deployed_agents: list) -> str:
597
+ """Generate Context-Aware Agent Selection guide from deployed agents.
598
+
599
+ Creates a mapping of task types to appropriate agents based on their
600
+ descriptions and capabilities.
601
+ """
602
+ guide = ""
603
+
604
+ # Build selection mapping based on deployed agents
605
+ selection_map = {}
606
+
607
+ for agent in deployed_agents:
608
+ agent_id = agent['id']
609
+ desc_lower = agent['description'].lower()
610
+
611
+ # Map task types to agents based on their descriptions
612
+ if 'implementation' in desc_lower or ('engineer' in agent_id and 'data' not in agent_id):
613
+ selection_map['Implementation tasks'] = f"{agent['display_name']} (`{agent_id}`)"
614
+ if 'codebase analysis' in desc_lower or 'research' in agent_id:
615
+ selection_map['Codebase analysis'] = f"{agent['display_name']} (`{agent_id}`)"
616
+ if 'testing' in desc_lower or 'qa' in agent_id:
617
+ selection_map['Testing/quality'] = f"{agent['display_name']} (`{agent_id}`)"
618
+ if 'documentation' in desc_lower:
619
+ selection_map['Documentation'] = f"{agent['display_name']} (`{agent_id}`)"
620
+ if 'security' in desc_lower or 'sast' in desc_lower:
621
+ selection_map['Security operations'] = f"{agent['display_name']} (`{agent_id}`)"
622
+ if 'deployment' in desc_lower or 'infrastructure' in desc_lower or 'ops' in agent_id:
623
+ selection_map['Deployment/infrastructure'] = f"{agent['display_name']} (`{agent_id}`)"
624
+ if 'data' in desc_lower and ('pipeline' in desc_lower or 'etl' in desc_lower):
625
+ selection_map['Data pipeline/ETL'] = f"{agent['display_name']} (`{agent_id}`)"
626
+ if 'git' in desc_lower or 'version control' in desc_lower:
627
+ selection_map['Version control'] = f"{agent['display_name']} (`{agent_id}`)"
628
+ if 'ticket' in desc_lower or 'epic' in desc_lower:
629
+ selection_map['Ticket/issue management'] = f"{agent['display_name']} (`{agent_id}`)"
630
+ if 'browser' in desc_lower or 'e2e' in desc_lower:
631
+ selection_map['Browser/E2E testing'] = f"{agent['display_name']} (`{agent_id}`)"
632
+ if 'frontend' in desc_lower or 'ui' in desc_lower or 'html' in desc_lower:
633
+ selection_map['Frontend/UI development'] = f"{agent['display_name']} (`{agent_id}`)"
634
+
635
+ # Always include PM questions
636
+ selection_map['PM questions'] = "Answer directly (only exception)"
637
+
638
+ # Format the selection guide
639
+ for task_type, agent_info in selection_map.items():
640
+ guide += f"- **{task_type}** → {agent_info}\n"
641
+
642
+ return guide
643
+
529
644
  def _get_fallback_capabilities(self) -> str:
530
645
  """Return fallback capabilities when dynamic discovery fails."""
531
646
  return """
@@ -535,13 +650,13 @@ Extract tickets from these patterns:
535
650
  You have the following specialized agents available for delegation:
536
651
 
537
652
  - **Engineer** (`engineer`): Code implementation and development
538
- - **Research** (`research`): Investigation and analysis
539
- - **QA** (`qa`): Testing and quality assurance
540
- - **Documentation** (`documentation`): Documentation creation and maintenance
541
- - **Security** (`security`): Security analysis and protection
542
- - **Data Engineer** (`data_engineer`): Data management and pipelines
543
- - **Ops** (`ops`): Deployment and operations
544
- - **Version Control** (`version_control`): Git operations and version management
653
+ - **Research** (`research-agent`): Investigation and analysis
654
+ - **QA** (`qa-agent`): Testing and quality assurance
655
+ - **Documentation** (`documentation-agent`): Documentation creation and maintenance
656
+ - **Security** (`security-agent`): Security analysis and protection
657
+ - **Data Engineer** (`data-engineer`): Data management and pipelines
658
+ - **Ops** (`ops-agent`): Deployment and operations
659
+ - **Version Control** (`version-control`): Git operations and version management
545
660
 
546
661
  **IMPORTANT**: Use the exact agent ID in parentheses when delegating tasks.
547
662
  """