claude-mpm 4.2.1__py3-none-any.whl → 4.2.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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/templates/agent-manager.json +1 -1
- claude_mpm/agents/templates/agentic_coder_optimizer.json +1 -1
- claude_mpm/agents/templates/api_qa.json +1 -1
- claude_mpm/agents/templates/code_analyzer.json +1 -1
- claude_mpm/agents/templates/data_engineer.json +1 -1
- claude_mpm/agents/templates/documentation.json +1 -1
- claude_mpm/agents/templates/engineer.json +2 -2
- claude_mpm/agents/templates/gcp_ops_agent.json +14 -9
- claude_mpm/agents/templates/imagemagick.json +1 -1
- claude_mpm/agents/templates/memory_manager.json +1 -1
- claude_mpm/agents/templates/ops.json +1 -1
- claude_mpm/agents/templates/project_organizer.json +1 -1
- claude_mpm/agents/templates/qa.json +2 -2
- claude_mpm/agents/templates/refactoring_engineer.json +1 -1
- claude_mpm/agents/templates/research.json +3 -3
- claude_mpm/agents/templates/security.json +1 -1
- claude_mpm/agents/templates/test-non-mpm.json +20 -0
- claude_mpm/agents/templates/ticketing.json +1 -1
- claude_mpm/agents/templates/vercel_ops_agent.json +2 -2
- claude_mpm/agents/templates/version_control.json +1 -1
- claude_mpm/agents/templates/web_qa.json +3 -8
- claude_mpm/agents/templates/web_ui.json +1 -1
- claude_mpm/cli/commands/agents.py +3 -0
- claude_mpm/cli/commands/dashboard.py +3 -3
- claude_mpm/cli/commands/monitor.py +227 -64
- claude_mpm/core/config.py +25 -0
- claude_mpm/core/unified_agent_registry.py +2 -2
- claude_mpm/dashboard/static/css/code-tree.css +220 -1
- claude_mpm/dashboard/static/css/dashboard.css +286 -0
- claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
- claude_mpm/dashboard/static/js/components/code-simple.js +507 -15
- claude_mpm/dashboard/static/js/components/code-tree.js +2044 -124
- claude_mpm/dashboard/static/js/socket-client.js +5 -2
- claude_mpm/dashboard/templates/code_simple.html +79 -0
- claude_mpm/dashboard/templates/index.html +42 -41
- claude_mpm/services/agents/deployment/agent_deployment.py +4 -1
- claude_mpm/services/agents/deployment/agent_discovery_service.py +101 -2
- claude_mpm/services/agents/deployment/agent_format_converter.py +53 -9
- claude_mpm/services/agents/deployment/agent_template_builder.py +355 -25
- claude_mpm/services/agents/deployment/agent_validator.py +11 -6
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +83 -15
- claude_mpm/services/agents/deployment/validation/template_validator.py +51 -40
- claude_mpm/services/cli/agent_listing_service.py +2 -2
- claude_mpm/services/dashboard/stable_server.py +389 -0
- claude_mpm/services/socketio/client_proxy.py +16 -0
- claude_mpm/services/socketio/dashboard_server.py +360 -0
- claude_mpm/services/socketio/handlers/code_analysis.py +27 -5
- claude_mpm/services/socketio/monitor_client.py +366 -0
- claude_mpm/services/socketio/monitor_server.py +505 -0
- claude_mpm/tools/code_tree_analyzer.py +95 -17
- {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/METADATA +1 -1
- {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/RECORD +57 -52
- {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/WHEEL +0 -0
- {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/top_level.txt +0 -0
|
@@ -8,6 +8,7 @@ maintainability and testability.
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
import json
|
|
11
|
+
import re
|
|
11
12
|
from pathlib import Path
|
|
12
13
|
from typing import Any, Dict, List
|
|
13
14
|
|
|
@@ -166,9 +167,11 @@ class AgentTemplateBuilder:
|
|
|
166
167
|
f"Tools must be comma-separated WITHOUT spaces: {tools_str}"
|
|
167
168
|
)
|
|
168
169
|
|
|
169
|
-
# Map model names to Claude Code format
|
|
170
|
+
# Map model names to Claude Code format (as required)
|
|
170
171
|
model_map = {
|
|
171
172
|
"claude-3-5-sonnet-20241022": "sonnet",
|
|
173
|
+
"claude-3-5-haiku-20241022": "haiku",
|
|
174
|
+
"claude-3-opus-20240229": "opus",
|
|
172
175
|
"claude-3-5-sonnet": "sonnet",
|
|
173
176
|
"claude-3-sonnet": "sonnet",
|
|
174
177
|
"claude-3-haiku": "haiku",
|
|
@@ -180,6 +183,9 @@ class AgentTemplateBuilder:
|
|
|
180
183
|
|
|
181
184
|
if model in model_map:
|
|
182
185
|
model = model_map[model]
|
|
186
|
+
else:
|
|
187
|
+
# Default to sonnet if model not found in map
|
|
188
|
+
model = "sonnet"
|
|
183
189
|
|
|
184
190
|
# Get response format from template or use base agent default
|
|
185
191
|
template_data.get("response", {}).get("format", "structured")
|
|
@@ -190,8 +196,6 @@ class AgentTemplateBuilder:
|
|
|
190
196
|
# CRITICAL: NO underscores allowed - they cause silent failures!
|
|
191
197
|
|
|
192
198
|
# Validate the name before proceeding
|
|
193
|
-
import re
|
|
194
|
-
|
|
195
199
|
if not re.match(r"^[a-z0-9]+(-[a-z0-9]+)*$", claude_code_name):
|
|
196
200
|
self.logger.error(
|
|
197
201
|
f"Invalid agent name '{claude_code_name}' - must match ^[a-z0-9]+(-[a-z0-9]+)*$"
|
|
@@ -201,12 +205,17 @@ class AgentTemplateBuilder:
|
|
|
201
205
|
)
|
|
202
206
|
|
|
203
207
|
# Extract description from template with fallback
|
|
204
|
-
|
|
208
|
+
raw_description = (
|
|
205
209
|
template_data.get("description")
|
|
206
210
|
or template_data.get("metadata", {}).get("description")
|
|
207
211
|
or f"{agent_name.title()} agent for specialized tasks"
|
|
208
212
|
)
|
|
209
213
|
|
|
214
|
+
# Convert to multiline format with examples for Claude Code compatibility
|
|
215
|
+
description = self._create_multiline_description(
|
|
216
|
+
raw_description, agent_name, template_data
|
|
217
|
+
)
|
|
218
|
+
|
|
210
219
|
# Extract custom metadata fields
|
|
211
220
|
metadata = template_data.get("metadata", {})
|
|
212
221
|
agent_version = (
|
|
@@ -258,39 +267,39 @@ class AgentTemplateBuilder:
|
|
|
258
267
|
# Include tools field only if agent is clearly restricted (missing core tools or very few tools)
|
|
259
268
|
include_tools_field = not has_core_tools or len(agent_tools) < 6
|
|
260
269
|
|
|
261
|
-
# Build YAML frontmatter using Claude Code's
|
|
270
|
+
# Build YAML frontmatter using Claude Code's compatible format
|
|
262
271
|
# ONLY include fields that Claude Code recognizes
|
|
263
272
|
#
|
|
264
273
|
# CLAUDE CODE COMPATIBLE FORMAT:
|
|
265
274
|
# - name: kebab-case agent name (required)
|
|
266
|
-
# - description: when/why to use this agent (required)
|
|
267
|
-
# -
|
|
268
|
-
# -
|
|
269
|
-
# -
|
|
275
|
+
# - description: when/why to use this agent with examples (required, multiline)
|
|
276
|
+
# - model: mapped model name (required)
|
|
277
|
+
# - color: visual identifier (optional)
|
|
278
|
+
# - version: agent version for update tracking (optional)
|
|
279
|
+
# - author: creator information (optional)
|
|
280
|
+
# NOTE: tags field REMOVED - not supported by Claude Code
|
|
270
281
|
frontmatter_lines = [
|
|
271
282
|
"---",
|
|
272
283
|
f"name: {claude_code_name}",
|
|
273
|
-
f"description: {description}",
|
|
274
|
-
f'version: "{agent_version}"',
|
|
275
284
|
]
|
|
276
285
|
|
|
277
|
-
# Add
|
|
286
|
+
# Add description as single-line YAML string with \n escapes
|
|
287
|
+
frontmatter_lines.append(
|
|
288
|
+
f"description: {self._format_description_for_yaml(description)}"
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
# Add model field (required for Claude Code)
|
|
292
|
+
frontmatter_lines.append(f"model: {model}")
|
|
293
|
+
|
|
294
|
+
# Add optional metadata (excluding tags field completely)
|
|
278
295
|
if metadata.get("color"):
|
|
279
296
|
frontmatter_lines.append(f"color: {metadata['color']}")
|
|
297
|
+
if (
|
|
298
|
+
agent_version and agent_version != "1.0.0"
|
|
299
|
+
): # Only include non-default versions
|
|
300
|
+
frontmatter_lines.append(f'version: "{agent_version}"')
|
|
280
301
|
if metadata.get("author"):
|
|
281
|
-
frontmatter_lines.append(f
|
|
282
|
-
if metadata.get("tags"):
|
|
283
|
-
frontmatter_lines.append("tags:")
|
|
284
|
-
for tag in metadata["tags"][:10]: # Limit to 10 tags
|
|
285
|
-
frontmatter_lines.append(f" - {tag}")
|
|
286
|
-
if metadata.get("priority"):
|
|
287
|
-
frontmatter_lines.append(f"priority: {metadata['priority']}")
|
|
288
|
-
if metadata.get("category"):
|
|
289
|
-
frontmatter_lines.append(f"category: {metadata['category']}")
|
|
290
|
-
|
|
291
|
-
# Only include tools if restricting to subset
|
|
292
|
-
if include_tools_field:
|
|
293
|
-
frontmatter_lines.append(f"tools: {tools_str}")
|
|
302
|
+
frontmatter_lines.append(f'author: "{metadata["author"]}"')
|
|
294
303
|
|
|
295
304
|
frontmatter_lines.extend(
|
|
296
305
|
[
|
|
@@ -545,3 +554,324 @@ tools:
|
|
|
545
554
|
formatted_items.append(f"{indent_str}- {item}")
|
|
546
555
|
|
|
547
556
|
return "\n".join(formatted_items)
|
|
557
|
+
|
|
558
|
+
def _create_multiline_description(
|
|
559
|
+
self, raw_description: str, agent_name: str, template_data: dict
|
|
560
|
+
) -> str:
|
|
561
|
+
"""
|
|
562
|
+
Create a comprehensive multiline description with examples for Claude Code compatibility.
|
|
563
|
+
Based on Claude's software-engineer.md format: detailed when/why description with examples.
|
|
564
|
+
|
|
565
|
+
Args:
|
|
566
|
+
raw_description: Original single-line description
|
|
567
|
+
agent_name: Name of the agent
|
|
568
|
+
template_data: Template data for extracting examples
|
|
569
|
+
|
|
570
|
+
Returns:
|
|
571
|
+
Formatted multiline description with examples in Claude Code format
|
|
572
|
+
"""
|
|
573
|
+
raw_description = self._format_to_single_line(raw_description)
|
|
574
|
+
|
|
575
|
+
# Get agent type for creating targeted descriptions
|
|
576
|
+
agent_type = template_data.get("agent_type", "general")
|
|
577
|
+
|
|
578
|
+
# Create enhanced description based on agent type
|
|
579
|
+
enhanced_description = self._create_enhanced_description(
|
|
580
|
+
raw_description, agent_name, agent_type, template_data
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
# Add examples
|
|
584
|
+
examples = self._extract_examples_from_template(template_data, agent_name)
|
|
585
|
+
if not examples:
|
|
586
|
+
examples = self._generate_default_examples(agent_name, template_data)
|
|
587
|
+
|
|
588
|
+
# Combine enhanced description with examples
|
|
589
|
+
if examples:
|
|
590
|
+
description_parts = [enhanced_description, ""] + examples
|
|
591
|
+
else:
|
|
592
|
+
description_parts = [enhanced_description]
|
|
593
|
+
|
|
594
|
+
return "\n".join(description_parts)
|
|
595
|
+
|
|
596
|
+
def _format_to_single_line(self, description: str) -> str:
|
|
597
|
+
"""
|
|
598
|
+
Format description to single line by removing line breaks and normalizing whitespace.
|
|
599
|
+
|
|
600
|
+
Args:
|
|
601
|
+
description: Raw description text
|
|
602
|
+
|
|
603
|
+
Returns:
|
|
604
|
+
Single-line formatted description
|
|
605
|
+
"""
|
|
606
|
+
if not description:
|
|
607
|
+
return description
|
|
608
|
+
|
|
609
|
+
# Remove all line breaks and normalize whitespace
|
|
610
|
+
single_line = " ".join(description.strip().split())
|
|
611
|
+
|
|
612
|
+
# Remove redundant spaces around punctuation
|
|
613
|
+
single_line = re.sub(r"\s+([,.!?;:])", r"\1", single_line)
|
|
614
|
+
single_line = re.sub(r"([,.!?;:])\s+", r"\1 ", single_line)
|
|
615
|
+
|
|
616
|
+
return single_line
|
|
617
|
+
|
|
618
|
+
def _create_enhanced_description(
|
|
619
|
+
self,
|
|
620
|
+
raw_description: str,
|
|
621
|
+
agent_name: str,
|
|
622
|
+
agent_type: str,
|
|
623
|
+
template_data: dict,
|
|
624
|
+
) -> str:
|
|
625
|
+
"""
|
|
626
|
+
Create an enhanced description based on agent type that follows Claude's format.
|
|
627
|
+
|
|
628
|
+
Args:
|
|
629
|
+
raw_description: Original description
|
|
630
|
+
agent_name: Name of the agent
|
|
631
|
+
agent_type: Type of agent (engineer, qa, research, etc.)
|
|
632
|
+
template_data: Template data for additional context
|
|
633
|
+
|
|
634
|
+
Returns:
|
|
635
|
+
Enhanced description string
|
|
636
|
+
"""
|
|
637
|
+
# Type-specific enhanced descriptions following Claude's software-engineer.md pattern
|
|
638
|
+
enhanced_descriptions = {
|
|
639
|
+
"engineer": "Use this agent when you need to implement new features, write production-quality code, refactor existing code, or solve complex programming challenges. This agent excels at translating requirements into well-architected, maintainable code solutions across various programming languages and frameworks.",
|
|
640
|
+
"qa": "Use this agent when you need comprehensive testing, quality assurance validation, or test automation. This agent specializes in creating robust test suites, identifying edge cases, and ensuring code quality through systematic testing approaches across different testing methodologies.",
|
|
641
|
+
"research": "Use this agent when you need to investigate codebases, analyze system architecture, or gather technical insights. This agent excels at code exploration, pattern identification, and providing comprehensive analysis of existing systems while maintaining strict memory efficiency.",
|
|
642
|
+
"ops": "Use this agent when you need infrastructure management, deployment automation, or operational excellence. This agent specializes in DevOps practices, cloud operations, monitoring setup, and maintaining reliable production systems.",
|
|
643
|
+
"security": "Use this agent when you need security analysis, vulnerability assessment, or secure coding practices. This agent excels at identifying security risks, implementing security best practices, and ensuring applications meet security standards.",
|
|
644
|
+
"documentation": "Use this agent when you need to create, update, or maintain technical documentation. This agent specializes in writing clear, comprehensive documentation including API docs, user guides, and technical specifications.",
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
# Get the enhanced description or fallback to the original with improvements
|
|
648
|
+
if agent_type in enhanced_descriptions:
|
|
649
|
+
return enhanced_descriptions[agent_type]
|
|
650
|
+
# Enhance the raw description if it's a custom type
|
|
651
|
+
if raw_description and len(raw_description) > 10:
|
|
652
|
+
return f"Use this agent when you need specialized assistance with {raw_description.lower()}. This agent provides targeted expertise and follows best practices for {agent_name.replace('-', ' ')} related tasks."
|
|
653
|
+
return f"Use this agent when you need specialized assistance from the {agent_name.replace('-', ' ')} agent. This agent provides targeted expertise and follows established best practices."
|
|
654
|
+
|
|
655
|
+
def _extract_examples_from_template(
|
|
656
|
+
self, template_data: dict, agent_name: str
|
|
657
|
+
) -> List[str]:
|
|
658
|
+
"""
|
|
659
|
+
Extract examples from template data and format with commentary.
|
|
660
|
+
Creates ONE example with commentary from template data.
|
|
661
|
+
|
|
662
|
+
Args:
|
|
663
|
+
template_data: Template data
|
|
664
|
+
agent_name: Name of the agent
|
|
665
|
+
|
|
666
|
+
Returns:
|
|
667
|
+
List of example strings (single example with commentary)
|
|
668
|
+
"""
|
|
669
|
+
examples = []
|
|
670
|
+
|
|
671
|
+
# Check for examples in knowledge section
|
|
672
|
+
knowledge = template_data.get("knowledge", {})
|
|
673
|
+
template_examples = knowledge.get("examples", [])
|
|
674
|
+
|
|
675
|
+
if template_examples:
|
|
676
|
+
# Take only the first example and add commentary
|
|
677
|
+
example = template_examples[0]
|
|
678
|
+
scenario = example.get("scenario", "")
|
|
679
|
+
approach = example.get("approach", "")
|
|
680
|
+
commentary = example.get("commentary", "")
|
|
681
|
+
|
|
682
|
+
if scenario and approach:
|
|
683
|
+
examples.extend(
|
|
684
|
+
[
|
|
685
|
+
"<example>",
|
|
686
|
+
f"Context: {scenario}",
|
|
687
|
+
f'user: "I need help with {scenario.lower()}"',
|
|
688
|
+
f'assistant: "I\'ll use the {agent_name} agent to {approach.lower()}."',
|
|
689
|
+
"<commentary>",
|
|
690
|
+
(
|
|
691
|
+
commentary
|
|
692
|
+
if commentary
|
|
693
|
+
else f"This agent is well-suited for {scenario.lower()} because it specializes in {approach.lower()} with targeted expertise."
|
|
694
|
+
),
|
|
695
|
+
"</commentary>",
|
|
696
|
+
"</example>",
|
|
697
|
+
]
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
# Check for triggers that can be converted to examples
|
|
701
|
+
interactions = template_data.get("interactions", {})
|
|
702
|
+
triggers = interactions.get("triggers", [])
|
|
703
|
+
|
|
704
|
+
if triggers and not examples:
|
|
705
|
+
# Convert first trigger to example with commentary
|
|
706
|
+
trigger = triggers[0]
|
|
707
|
+
|
|
708
|
+
# Handle both string and dict trigger formats
|
|
709
|
+
if isinstance(trigger, dict):
|
|
710
|
+
# New format with pattern and confidence
|
|
711
|
+
trigger_text = trigger.get("pattern", "")
|
|
712
|
+
else:
|
|
713
|
+
# Old format with simple string
|
|
714
|
+
trigger_text = str(trigger)
|
|
715
|
+
|
|
716
|
+
# Skip if we don't have valid trigger text
|
|
717
|
+
if not trigger_text:
|
|
718
|
+
return examples
|
|
719
|
+
|
|
720
|
+
agent_type = template_data.get("agent_type", "general")
|
|
721
|
+
|
|
722
|
+
examples.extend(
|
|
723
|
+
[
|
|
724
|
+
"<example>",
|
|
725
|
+
f"Context: When user needs {trigger_text}",
|
|
726
|
+
f'user: "{trigger_text}"',
|
|
727
|
+
f'assistant: "I\'ll use the {agent_name} agent for {trigger_text}."',
|
|
728
|
+
"<commentary>",
|
|
729
|
+
f"This {agent_type} agent is appropriate because it has specialized capabilities for {trigger_text.lower()} tasks.",
|
|
730
|
+
"</commentary>",
|
|
731
|
+
"</example>",
|
|
732
|
+
]
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
return examples
|
|
736
|
+
|
|
737
|
+
def _generate_default_examples(
|
|
738
|
+
self, agent_name: str, template_data: dict
|
|
739
|
+
) -> List[str]:
|
|
740
|
+
"""
|
|
741
|
+
Generate default examples when none are available in template.
|
|
742
|
+
Creates ONE example with commentary for each agent type.
|
|
743
|
+
|
|
744
|
+
Args:
|
|
745
|
+
agent_name: Name of the agent
|
|
746
|
+
template_data: Template data for context
|
|
747
|
+
|
|
748
|
+
Returns:
|
|
749
|
+
List of example strings (single example with commentary)
|
|
750
|
+
"""
|
|
751
|
+
agent_type = template_data.get("agent_type", "general")
|
|
752
|
+
|
|
753
|
+
# Create type-specific examples with commentary inside
|
|
754
|
+
type_examples = {
|
|
755
|
+
"engineer": [
|
|
756
|
+
"<example>",
|
|
757
|
+
"Context: When you need to implement new features or write code.",
|
|
758
|
+
'user: "I need to add authentication to my API"',
|
|
759
|
+
f'assistant: "I\'ll use the {agent_name} agent to implement a secure authentication system for your API."',
|
|
760
|
+
"<commentary>",
|
|
761
|
+
"The engineer agent is ideal for code implementation tasks because it specializes in writing production-quality code, following best practices, and creating well-architected solutions.",
|
|
762
|
+
"</commentary>",
|
|
763
|
+
"</example>",
|
|
764
|
+
],
|
|
765
|
+
"ops": [
|
|
766
|
+
"<example>",
|
|
767
|
+
"Context: When you need to deploy or manage infrastructure.",
|
|
768
|
+
'user: "I need to deploy my application to the cloud"',
|
|
769
|
+
f'assistant: "I\'ll use the {agent_name} agent to set up and deploy your application infrastructure."',
|
|
770
|
+
"<commentary>",
|
|
771
|
+
"The ops agent excels at infrastructure management and deployment automation, ensuring reliable and scalable production systems.",
|
|
772
|
+
"</commentary>",
|
|
773
|
+
"</example>",
|
|
774
|
+
],
|
|
775
|
+
"qa": [
|
|
776
|
+
"<example>",
|
|
777
|
+
"Context: When you need to test or validate functionality.",
|
|
778
|
+
'user: "I need to write tests for my new feature"',
|
|
779
|
+
f'assistant: "I\'ll use the {agent_name} agent to create comprehensive tests for your feature."',
|
|
780
|
+
"<commentary>",
|
|
781
|
+
"The QA agent specializes in comprehensive testing strategies, quality assurance validation, and creating robust test suites that ensure code reliability.",
|
|
782
|
+
"</commentary>",
|
|
783
|
+
"</example>",
|
|
784
|
+
],
|
|
785
|
+
"research": [
|
|
786
|
+
"<example>",
|
|
787
|
+
"Context: When you need to investigate or analyze existing codebases.",
|
|
788
|
+
'user: "I need to understand how the authentication system works in this project"',
|
|
789
|
+
f'assistant: "I\'ll use the {agent_name} agent to analyze the codebase and explain the authentication implementation."',
|
|
790
|
+
"<commentary>",
|
|
791
|
+
"The research agent is perfect for code exploration and analysis tasks, providing thorough investigation of existing systems while maintaining memory efficiency.",
|
|
792
|
+
"</commentary>",
|
|
793
|
+
"</example>",
|
|
794
|
+
],
|
|
795
|
+
"security": [
|
|
796
|
+
"<example>",
|
|
797
|
+
"Context: When you need to review code for security vulnerabilities.",
|
|
798
|
+
'user: "I need a security review of my authentication implementation"',
|
|
799
|
+
f'assistant: "I\'ll use the {agent_name} agent to conduct a thorough security analysis of your authentication code."',
|
|
800
|
+
"<commentary>",
|
|
801
|
+
"The security agent specializes in identifying security risks, vulnerability assessment, and ensuring applications meet security standards and best practices.",
|
|
802
|
+
"</commentary>",
|
|
803
|
+
"</example>",
|
|
804
|
+
],
|
|
805
|
+
"documentation": [
|
|
806
|
+
"<example>",
|
|
807
|
+
"Context: When you need to create or update technical documentation.",
|
|
808
|
+
'user: "I need to document this new API endpoint"',
|
|
809
|
+
f'assistant: "I\'ll use the {agent_name} agent to create comprehensive API documentation."',
|
|
810
|
+
"<commentary>",
|
|
811
|
+
"The documentation agent excels at creating clear, comprehensive technical documentation including API docs, user guides, and technical specifications.",
|
|
812
|
+
"</commentary>",
|
|
813
|
+
"</example>",
|
|
814
|
+
],
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
return type_examples.get(
|
|
818
|
+
agent_type,
|
|
819
|
+
[
|
|
820
|
+
"<example>",
|
|
821
|
+
f"Context: When you need specialized assistance from the {agent_name} agent.",
|
|
822
|
+
f'user: "I need help with {agent_name.replace("-", " ")} tasks"',
|
|
823
|
+
f'assistant: "I\'ll use the {agent_name} agent to provide specialized assistance."',
|
|
824
|
+
"<commentary>",
|
|
825
|
+
f"This agent provides targeted expertise for {agent_name.replace('-', ' ')} related tasks and follows established best practices.",
|
|
826
|
+
"</commentary>",
|
|
827
|
+
"</example>",
|
|
828
|
+
],
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
def _indent_multiline_text(self, text: str, spaces: int) -> str:
|
|
832
|
+
"""
|
|
833
|
+
Indent multiline text with specified number of spaces.
|
|
834
|
+
|
|
835
|
+
Args:
|
|
836
|
+
text: Text to indent
|
|
837
|
+
spaces: Number of spaces for indentation
|
|
838
|
+
|
|
839
|
+
Returns:
|
|
840
|
+
Indented text
|
|
841
|
+
"""
|
|
842
|
+
if not text:
|
|
843
|
+
return ""
|
|
844
|
+
|
|
845
|
+
indent = " " * spaces
|
|
846
|
+
lines = text.split("\n")
|
|
847
|
+
indented_lines = []
|
|
848
|
+
|
|
849
|
+
for line in lines:
|
|
850
|
+
if line.strip(): # Non-empty lines get indented
|
|
851
|
+
indented_lines.append(indent + line)
|
|
852
|
+
else: # Empty lines stay empty
|
|
853
|
+
indented_lines.append("")
|
|
854
|
+
|
|
855
|
+
return "\n".join(indented_lines)
|
|
856
|
+
|
|
857
|
+
def _format_description_for_yaml(self, description: str) -> str:
|
|
858
|
+
"""Format description as a single-line YAML string with escaped newlines.
|
|
859
|
+
|
|
860
|
+
Args:
|
|
861
|
+
description: Multi-line description text
|
|
862
|
+
|
|
863
|
+
Returns:
|
|
864
|
+
Single-line YAML-formatted string with \n escapes
|
|
865
|
+
"""
|
|
866
|
+
if not description:
|
|
867
|
+
return '""'
|
|
868
|
+
|
|
869
|
+
# The description already contains actual newlines, we need to escape them
|
|
870
|
+
# Replace actual newlines with \n escape sequence
|
|
871
|
+
escaped = description.replace("\n", "\\n")
|
|
872
|
+
|
|
873
|
+
# Escape any quotes in the description
|
|
874
|
+
escaped = escaped.replace('"', '\\"')
|
|
875
|
+
|
|
876
|
+
# Return as quoted string
|
|
877
|
+
return f'"{escaped}"'
|
|
@@ -331,12 +331,17 @@ class AgentValidator:
|
|
|
331
331
|
# Extract from YAML frontmatter
|
|
332
332
|
lines = content.split("\n")
|
|
333
333
|
for line in lines:
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
334
|
+
stripped_line = line.strip()
|
|
335
|
+
if stripped_line.startswith("name:"):
|
|
336
|
+
agent_info["name"] = stripped_line.split(":", 1)[1].strip().strip("\"'")
|
|
337
|
+
elif stripped_line.startswith("description:"):
|
|
338
|
+
agent_info["description"] = (
|
|
339
|
+
stripped_line.split(":", 1)[1].strip().strip("\"'")
|
|
340
|
+
)
|
|
341
|
+
elif stripped_line.startswith("version:"):
|
|
342
|
+
agent_info["version"] = (
|
|
343
|
+
stripped_line.split(":", 1)[1].strip().strip("\"'")
|
|
344
|
+
)
|
|
340
345
|
|
|
341
346
|
return agent_info
|
|
342
347
|
|
|
@@ -469,6 +469,47 @@ class MultiSourceAgentDeploymentService:
|
|
|
469
469
|
|
|
470
470
|
return cleanup_results
|
|
471
471
|
|
|
472
|
+
def _is_user_created_agent(self, agent_file: Path) -> bool:
|
|
473
|
+
"""Check if an agent is user-created based on metadata.
|
|
474
|
+
|
|
475
|
+
User agents are identified by:
|
|
476
|
+
- Lack of MPM authorship indicators
|
|
477
|
+
- Missing version or v0.0.0
|
|
478
|
+
- Certain naming patterns
|
|
479
|
+
|
|
480
|
+
Args:
|
|
481
|
+
agent_file: Path to the agent file to check
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
True if the agent appears to be user-created, False otherwise
|
|
485
|
+
"""
|
|
486
|
+
try:
|
|
487
|
+
content = agent_file.read_text()
|
|
488
|
+
|
|
489
|
+
# Check for MPM authorship indicators
|
|
490
|
+
mpm_indicators = [
|
|
491
|
+
"author: claude-mpm",
|
|
492
|
+
"author: 'claude-mpm'",
|
|
493
|
+
'author: "claude-mpm"',
|
|
494
|
+
"author: Claude MPM",
|
|
495
|
+
"Claude MPM Team",
|
|
496
|
+
"Generated by Claude MPM",
|
|
497
|
+
"claude-mpm-project",
|
|
498
|
+
]
|
|
499
|
+
|
|
500
|
+
for indicator in mpm_indicators:
|
|
501
|
+
if indicator.lower() in content.lower():
|
|
502
|
+
return False # This is an MPM agent
|
|
503
|
+
|
|
504
|
+
# Check for version 0.0.0 (typical user agent default)
|
|
505
|
+
if "version: 0.0.0" in content or "version: '0.0.0'" in content:
|
|
506
|
+
return True
|
|
507
|
+
|
|
508
|
+
return True # Default to user-created if no MPM indicators
|
|
509
|
+
|
|
510
|
+
except Exception:
|
|
511
|
+
return True # Default to user-created if we can't read it
|
|
512
|
+
|
|
472
513
|
def _apply_config_filters(
|
|
473
514
|
self, selected_agents: Dict[str, Dict[str, Any]], config: Config
|
|
474
515
|
) -> Dict[str, Dict[str, Any]]:
|
|
@@ -534,7 +575,8 @@ class MultiSourceAgentDeploymentService:
|
|
|
534
575
|
"needs_update": [],
|
|
535
576
|
"up_to_date": [],
|
|
536
577
|
"new_agents": [],
|
|
537
|
-
"orphaned_agents": [], #
|
|
578
|
+
"orphaned_agents": [], # System agents without templates
|
|
579
|
+
"user_agents": [], # User-created agents (no templates required)
|
|
538
580
|
"version_upgrades": [],
|
|
539
581
|
"version_downgrades": [],
|
|
540
582
|
"source_changes": [],
|
|
@@ -655,10 +697,11 @@ class MultiSourceAgentDeploymentService:
|
|
|
655
697
|
)
|
|
656
698
|
|
|
657
699
|
# Check for orphaned agents (deployed but no template)
|
|
658
|
-
|
|
700
|
+
system_orphaned, user_orphaned = self._detect_orphaned_agents_simple(
|
|
659
701
|
deployed_agents_dir, agents_to_deploy
|
|
660
702
|
)
|
|
661
|
-
comparison_results["orphaned_agents"] =
|
|
703
|
+
comparison_results["orphaned_agents"] = system_orphaned
|
|
704
|
+
comparison_results["user_agents"] = user_orphaned
|
|
662
705
|
|
|
663
706
|
# Log summary
|
|
664
707
|
summary_parts = [
|
|
@@ -668,7 +711,11 @@ class MultiSourceAgentDeploymentService:
|
|
|
668
711
|
]
|
|
669
712
|
if comparison_results["orphaned_agents"]:
|
|
670
713
|
summary_parts.append(
|
|
671
|
-
f"{len(comparison_results['orphaned_agents'])} orphaned"
|
|
714
|
+
f"{len(comparison_results['orphaned_agents'])} system orphaned"
|
|
715
|
+
)
|
|
716
|
+
if comparison_results["user_agents"]:
|
|
717
|
+
summary_parts.append(
|
|
718
|
+
f"{len(comparison_results['user_agents'])} user agents"
|
|
672
719
|
)
|
|
673
720
|
|
|
674
721
|
self.logger.info(f"Version comparison complete: {', '.join(summary_parts)}")
|
|
@@ -698,10 +745,10 @@ class MultiSourceAgentDeploymentService:
|
|
|
698
745
|
f"{downgrade['template_version']} (keeping deployed version)"
|
|
699
746
|
)
|
|
700
747
|
|
|
701
|
-
# Log orphaned agents if found
|
|
748
|
+
# Log system orphaned agents if found
|
|
702
749
|
if comparison_results["orphaned_agents"]:
|
|
703
750
|
self.logger.info(
|
|
704
|
-
f"Found {len(comparison_results['orphaned_agents'])} orphaned agent(s) "
|
|
751
|
+
f"Found {len(comparison_results['orphaned_agents'])} system orphaned agent(s) "
|
|
705
752
|
f"(deployed without templates):"
|
|
706
753
|
)
|
|
707
754
|
for orphan in comparison_results["orphaned_agents"]:
|
|
@@ -710,6 +757,18 @@ class MultiSourceAgentDeploymentService:
|
|
|
710
757
|
f"(consider removing or creating a template)"
|
|
711
758
|
)
|
|
712
759
|
|
|
760
|
+
# Log user agents at debug level if found
|
|
761
|
+
if comparison_results["user_agents"]:
|
|
762
|
+
self.logger.debug(
|
|
763
|
+
f"Found {len(comparison_results['user_agents'])} user-created agent(s) "
|
|
764
|
+
f"(no templates required):"
|
|
765
|
+
)
|
|
766
|
+
for user_agent in comparison_results["user_agents"]:
|
|
767
|
+
self.logger.debug(
|
|
768
|
+
f" - {user_agent['name']} v{user_agent['version']} "
|
|
769
|
+
f"(user-created agent)"
|
|
770
|
+
)
|
|
771
|
+
|
|
713
772
|
return comparison_results
|
|
714
773
|
|
|
715
774
|
def _infer_agent_source_from_context(
|
|
@@ -860,7 +919,7 @@ class MultiSourceAgentDeploymentService:
|
|
|
860
919
|
|
|
861
920
|
def _detect_orphaned_agents_simple(
|
|
862
921
|
self, deployed_agents_dir: Path, agents_to_deploy: Dict[str, Path]
|
|
863
|
-
) -> List[Dict[str, Any]]:
|
|
922
|
+
) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
|
|
864
923
|
"""Simple orphan detection that works with agents_to_deploy structure.
|
|
865
924
|
|
|
866
925
|
Args:
|
|
@@ -868,12 +927,13 @@ class MultiSourceAgentDeploymentService:
|
|
|
868
927
|
agents_to_deploy: Dictionary mapping file stems to template paths
|
|
869
928
|
|
|
870
929
|
Returns:
|
|
871
|
-
|
|
930
|
+
Tuple of (system_orphaned_agents, user_orphaned_agents)
|
|
872
931
|
"""
|
|
873
|
-
|
|
932
|
+
system_orphaned = []
|
|
933
|
+
user_orphaned = []
|
|
874
934
|
|
|
875
935
|
if not deployed_agents_dir.exists():
|
|
876
|
-
return
|
|
936
|
+
return system_orphaned, user_orphaned
|
|
877
937
|
|
|
878
938
|
# agents_to_deploy already contains file stems as keys
|
|
879
939
|
available_stems = set(agents_to_deploy.keys())
|
|
@@ -885,7 +945,7 @@ class MultiSourceAgentDeploymentService:
|
|
|
885
945
|
if agent_stem in available_stems:
|
|
886
946
|
continue
|
|
887
947
|
|
|
888
|
-
# This is an orphaned agent
|
|
948
|
+
# This is an orphaned agent - determine if it's user-created or system
|
|
889
949
|
try:
|
|
890
950
|
deployed_content = deployed_file.read_text()
|
|
891
951
|
deployed_version, _, _ = (
|
|
@@ -899,11 +959,19 @@ class MultiSourceAgentDeploymentService:
|
|
|
899
959
|
except Exception:
|
|
900
960
|
version_str = "unknown"
|
|
901
961
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
962
|
+
orphan_info = {
|
|
963
|
+
"name": agent_stem,
|
|
964
|
+
"file": str(deployed_file),
|
|
965
|
+
"version": version_str,
|
|
966
|
+
}
|
|
905
967
|
|
|
906
|
-
|
|
968
|
+
# Determine if this is a user-created agent
|
|
969
|
+
if self._is_user_created_agent(deployed_file):
|
|
970
|
+
user_orphaned.append(orphan_info)
|
|
971
|
+
else:
|
|
972
|
+
system_orphaned.append(orphan_info)
|
|
973
|
+
|
|
974
|
+
return system_orphaned, user_orphaned
|
|
907
975
|
|
|
908
976
|
def cleanup_orphaned_agents(
|
|
909
977
|
self, deployed_agents_dir: Path, dry_run: bool = True
|