claude-mpm 4.15.2__py3-none-any.whl → 4.20.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/BASE_ENGINEER.md +286 -0
- claude_mpm/agents/BASE_PM.md +255 -23
- claude_mpm/agents/PM_INSTRUCTIONS.md +40 -0
- claude_mpm/agents/agent_loader.py +4 -4
- claude_mpm/agents/templates/agentic-coder-optimizer.json +9 -2
- claude_mpm/agents/templates/api_qa.json +7 -1
- claude_mpm/agents/templates/clerk-ops.json +8 -1
- claude_mpm/agents/templates/code_analyzer.json +4 -1
- claude_mpm/agents/templates/dart_engineer.json +11 -1
- claude_mpm/agents/templates/data_engineer.json +11 -1
- claude_mpm/agents/templates/documentation.json +6 -1
- claude_mpm/agents/templates/engineer.json +18 -1
- claude_mpm/agents/templates/gcp_ops_agent.json +8 -1
- claude_mpm/agents/templates/golang_engineer.json +11 -1
- claude_mpm/agents/templates/java_engineer.json +12 -2
- claude_mpm/agents/templates/local_ops_agent.json +216 -37
- claude_mpm/agents/templates/nextjs_engineer.json +11 -1
- claude_mpm/agents/templates/ops.json +8 -1
- claude_mpm/agents/templates/php-engineer.json +11 -1
- claude_mpm/agents/templates/project_organizer.json +9 -2
- claude_mpm/agents/templates/prompt-engineer.json +5 -1
- claude_mpm/agents/templates/python_engineer.json +19 -4
- claude_mpm/agents/templates/qa.json +7 -1
- claude_mpm/agents/templates/react_engineer.json +11 -1
- claude_mpm/agents/templates/refactoring_engineer.json +8 -1
- claude_mpm/agents/templates/research.json +4 -1
- claude_mpm/agents/templates/ruby-engineer.json +11 -1
- claude_mpm/agents/templates/rust_engineer.json +23 -8
- claude_mpm/agents/templates/security.json +6 -1
- claude_mpm/agents/templates/svelte-engineer.json +225 -0
- claude_mpm/agents/templates/ticketing.json +6 -1
- claude_mpm/agents/templates/typescript_engineer.json +11 -1
- claude_mpm/agents/templates/vercel_ops_agent.json +8 -1
- claude_mpm/agents/templates/version_control.json +8 -1
- claude_mpm/agents/templates/web_qa.json +7 -1
- claude_mpm/agents/templates/web_ui.json +11 -1
- claude_mpm/cli/commands/__init__.py +2 -0
- claude_mpm/cli/commands/configure.py +164 -16
- claude_mpm/cli/commands/configure_agent_display.py +6 -6
- claude_mpm/cli/commands/configure_behavior_manager.py +8 -8
- claude_mpm/cli/commands/configure_navigation.py +20 -18
- claude_mpm/cli/commands/configure_startup_manager.py +14 -14
- claude_mpm/cli/commands/configure_template_editor.py +8 -8
- claude_mpm/cli/commands/mpm_init.py +109 -24
- claude_mpm/cli/commands/skills.py +434 -0
- claude_mpm/cli/executor.py +2 -0
- claude_mpm/cli/interactive/__init__.py +3 -0
- claude_mpm/cli/interactive/skills_wizard.py +491 -0
- claude_mpm/cli/parsers/base_parser.py +7 -0
- claude_mpm/cli/parsers/skills_parser.py +137 -0
- claude_mpm/cli/startup.py +83 -0
- claude_mpm/commands/mpm-auto-configure.md +52 -0
- claude_mpm/commands/mpm-help.md +3 -0
- claude_mpm/commands/mpm-init.md +112 -6
- claude_mpm/commands/mpm-version.md +113 -0
- claude_mpm/commands/mpm.md +1 -0
- claude_mpm/config/agent_config.py +2 -2
- claude_mpm/constants.py +12 -0
- claude_mpm/core/config.py +42 -0
- claude_mpm/core/enums.py +18 -0
- claude_mpm/core/factories.py +1 -1
- claude_mpm/core/optimized_agent_loader.py +3 -3
- claude_mpm/core/types.py +2 -9
- claude_mpm/dashboard/static/js/dashboard.js +0 -14
- claude_mpm/dashboard/templates/index.html +3 -41
- claude_mpm/hooks/__init__.py +8 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
- claude_mpm/hooks/session_resume_hook.py +121 -0
- claude_mpm/models/resume_log.py +340 -0
- claude_mpm/services/agents/auto_config_manager.py +1 -1
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
- claude_mpm/services/agents/deployment/agent_record_service.py +1 -1
- claude_mpm/services/agents/deployment/agent_validator.py +17 -1
- claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
- claude_mpm/services/agents/deployment/local_template_deployment.py +1 -1
- claude_mpm/services/agents/deployment/validation/__init__.py +3 -1
- claude_mpm/services/agents/deployment/validation/validation_result.py +1 -9
- claude_mpm/services/agents/local_template_manager.py +1 -1
- claude_mpm/services/agents/recommender.py +47 -0
- claude_mpm/services/cli/resume_service.py +617 -0
- claude_mpm/services/cli/session_manager.py +87 -0
- claude_mpm/services/cli/session_resume_helper.py +352 -0
- claude_mpm/services/core/models/health.py +1 -28
- claude_mpm/services/core/path_resolver.py +1 -1
- claude_mpm/services/infrastructure/monitoring/__init__.py +1 -1
- claude_mpm/services/infrastructure/monitoring/aggregator.py +12 -12
- claude_mpm/services/infrastructure/monitoring/base.py +5 -13
- claude_mpm/services/infrastructure/monitoring/network.py +7 -6
- claude_mpm/services/infrastructure/monitoring/process.py +13 -12
- claude_mpm/services/infrastructure/monitoring/resources.py +7 -6
- claude_mpm/services/infrastructure/monitoring/service.py +16 -15
- claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
- claude_mpm/services/local_ops/__init__.py +1 -1
- claude_mpm/services/local_ops/crash_detector.py +1 -1
- claude_mpm/services/local_ops/health_checks/http_check.py +2 -1
- claude_mpm/services/local_ops/health_checks/process_check.py +2 -1
- claude_mpm/services/local_ops/health_checks/resource_check.py +2 -1
- claude_mpm/services/local_ops/health_manager.py +1 -1
- claude_mpm/services/local_ops/restart_manager.py +1 -1
- claude_mpm/services/mcp_config_manager.py +7 -131
- claude_mpm/services/session_manager.py +205 -1
- claude_mpm/services/shared/async_service_base.py +16 -27
- claude_mpm/services/shared/lifecycle_service_base.py +1 -14
- claude_mpm/services/socketio/handlers/__init__.py +5 -2
- claude_mpm/services/socketio/handlers/hook.py +10 -0
- claude_mpm/services/socketio/handlers/registry.py +4 -2
- claude_mpm/services/socketio/server/main.py +7 -7
- claude_mpm/services/unified/deployment_strategies/local.py +1 -1
- claude_mpm/services/version_service.py +104 -1
- claude_mpm/skills/__init__.py +42 -0
- claude_mpm/skills/agent_skills_injector.py +331 -0
- claude_mpm/skills/bundled/LICENSE_ATTRIBUTIONS.md +79 -0
- claude_mpm/skills/bundled/__init__.py +6 -0
- claude_mpm/skills/bundled/api-documentation.md +393 -0
- claude_mpm/skills/bundled/async-testing.md +571 -0
- claude_mpm/skills/bundled/code-review.md +143 -0
- claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +75 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +184 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +107 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/code-reviewer.md +146 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +118 -0
- claude_mpm/skills/bundled/database-migration.md +199 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +177 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +175 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/common-failures.md +213 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +314 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +227 -0
- claude_mpm/skills/bundled/docker-containerization.md +194 -0
- claude_mpm/skills/bundled/express-local-dev.md +1429 -0
- claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
- claude_mpm/skills/bundled/git-workflow.md +414 -0
- claude_mpm/skills/bundled/imagemagick.md +204 -0
- claude_mpm/skills/bundled/json-data-handling.md +223 -0
- claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +74 -0
- claude_mpm/skills/bundled/main/internal-comms/SKILL.md +32 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
- claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +328 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
- claude_mpm/skills/bundled/main/mcp-builder/scripts/connections.py +150 -0
- claude_mpm/skills/bundled/main/mcp-builder/scripts/evaluation.py +372 -0
- claude_mpm/skills/bundled/main/skill-creator/SKILL.md +209 -0
- claude_mpm/skills/bundled/main/skill-creator/scripts/init_skill.py +302 -0
- claude_mpm/skills/bundled/main/skill-creator/scripts/package_skill.py +111 -0
- claude_mpm/skills/bundled/main/skill-creator/scripts/quick_validate.py +65 -0
- claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
- claude_mpm/skills/bundled/pdf.md +141 -0
- claude_mpm/skills/bundled/performance-profiling.md +567 -0
- claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
- claude_mpm/skills/bundled/security-scanning.md +327 -0
- claude_mpm/skills/bundled/systematic-debugging.md +473 -0
- claude_mpm/skills/bundled/test-driven-development.md +378 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +123 -0
- claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +304 -0
- claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +96 -0
- claude_mpm/skills/bundled/testing/webapp-testing/examples/console_logging.py +35 -0
- claude_mpm/skills/bundled/testing/webapp-testing/examples/element_discovery.py +40 -0
- claude_mpm/skills/bundled/testing/webapp-testing/examples/static_html_automation.py +34 -0
- claude_mpm/skills/bundled/testing/webapp-testing/scripts/with_server.py +107 -0
- claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
- claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
- claude_mpm/skills/bundled/xlsx.md +157 -0
- claude_mpm/skills/registry.py +286 -0
- claude_mpm/skills/skill_manager.py +310 -0
- claude_mpm/skills/skills_registry.py +351 -0
- claude_mpm/skills/skills_service.py +730 -0
- claude_mpm/utils/agent_dependency_loader.py +2 -2
- {claude_mpm-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/METADATA +211 -33
- {claude_mpm-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/RECORD +195 -115
- claude_mpm/agents/INSTRUCTIONS_OLD_DEPRECATED.md +0 -602
- claude_mpm/dashboard/static/css/code-tree.css +0 -1639
- claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +0 -353
- claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +0 -235
- claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +0 -409
- claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +0 -435
- claude_mpm/dashboard/static/js/components/code-tree.js +0 -5869
- claude_mpm/dashboard/static/js/components/code-viewer.js +0 -1386
- {claude_mpm-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/WHEEL +0 -0
- {claude_mpm-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"""Skills manager - integrates skills with agents."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
from claude_mpm.core.logging_utils import get_logger
|
|
8
|
+
|
|
9
|
+
from .registry import Skill, get_registry
|
|
10
|
+
|
|
11
|
+
logger = get_logger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SkillManager:
|
|
15
|
+
"""Manages skills and their integration with agents."""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
"""Initialize the skill manager."""
|
|
19
|
+
self.registry = get_registry()
|
|
20
|
+
self.agent_skill_mapping: Dict[str, List[str]] = {}
|
|
21
|
+
self._load_agent_mappings()
|
|
22
|
+
|
|
23
|
+
def _load_agent_mappings(self):
|
|
24
|
+
"""Load skill mappings from agent templates."""
|
|
25
|
+
# Load mappings from agent JSON templates that have 'skills' field
|
|
26
|
+
agent_templates_dir = Path(__file__).parent.parent / "agents" / "templates"
|
|
27
|
+
|
|
28
|
+
if not agent_templates_dir.exists():
|
|
29
|
+
logger.warning(
|
|
30
|
+
f"Agent templates directory not found: {agent_templates_dir}"
|
|
31
|
+
)
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
mapping_count = 0
|
|
35
|
+
for template_file in agent_templates_dir.glob("*.json"):
|
|
36
|
+
try:
|
|
37
|
+
with open(template_file, encoding="utf-8") as f:
|
|
38
|
+
agent_data = json.load(f)
|
|
39
|
+
|
|
40
|
+
agent_id = agent_data.get("agent_id") or agent_data.get("agent_type")
|
|
41
|
+
if not agent_id:
|
|
42
|
+
continue
|
|
43
|
+
|
|
44
|
+
# Extract skills list if present
|
|
45
|
+
skills = agent_data.get("skills", [])
|
|
46
|
+
if skills:
|
|
47
|
+
self.agent_skill_mapping[agent_id] = skills
|
|
48
|
+
mapping_count += 1
|
|
49
|
+
logger.debug(
|
|
50
|
+
f"Agent '{agent_id}' mapped to {len(skills)} skills: {', '.join(skills)}"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
except Exception as e:
|
|
54
|
+
logger.error(f"Error loading agent mapping from {template_file}: {e}")
|
|
55
|
+
|
|
56
|
+
if mapping_count > 0:
|
|
57
|
+
logger.info(f"Loaded skill mappings for {mapping_count} agents")
|
|
58
|
+
|
|
59
|
+
def get_agent_skills(self, agent_type: str) -> List[Skill]:
|
|
60
|
+
"""
|
|
61
|
+
Get all skills for an agent (bundled + discovered).
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
agent_type: Agent type/ID (e.g., 'engineer', 'python_engineer')
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
List of Skill objects for this agent
|
|
68
|
+
"""
|
|
69
|
+
skill_names = self.agent_skill_mapping.get(agent_type, [])
|
|
70
|
+
|
|
71
|
+
# Get skills from registry
|
|
72
|
+
skills = []
|
|
73
|
+
for name in skill_names:
|
|
74
|
+
skill = self.registry.get_skill(name)
|
|
75
|
+
if skill:
|
|
76
|
+
skills.append(skill)
|
|
77
|
+
else:
|
|
78
|
+
logger.warning(
|
|
79
|
+
f"Skill '{name}' referenced by agent '{agent_type}' not found"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Also include skills that have no agent restriction
|
|
83
|
+
# or explicitly list this agent type
|
|
84
|
+
additional_skills = self.registry.get_skills_for_agent(agent_type)
|
|
85
|
+
for skill in additional_skills:
|
|
86
|
+
if skill not in skills:
|
|
87
|
+
skills.append(skill)
|
|
88
|
+
|
|
89
|
+
return skills
|
|
90
|
+
|
|
91
|
+
def enhance_agent_prompt(
|
|
92
|
+
self, agent_type: str, base_prompt: str, include_all: bool = False
|
|
93
|
+
) -> str:
|
|
94
|
+
"""
|
|
95
|
+
Enhance agent prompt with available skills.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
agent_type: Agent type/ID
|
|
99
|
+
base_prompt: Original agent prompt
|
|
100
|
+
include_all: If True, include all available skills regardless of mapping
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Enhanced prompt with skills section appended
|
|
104
|
+
"""
|
|
105
|
+
if include_all:
|
|
106
|
+
skills = self.registry.list_skills()
|
|
107
|
+
else:
|
|
108
|
+
skills = self.get_agent_skills(agent_type)
|
|
109
|
+
|
|
110
|
+
if not skills:
|
|
111
|
+
return base_prompt
|
|
112
|
+
|
|
113
|
+
# Build skills section
|
|
114
|
+
skills_section = "\n\n" + "=" * 80 + "\n"
|
|
115
|
+
skills_section += "## 🎯 Available Skills\n\n"
|
|
116
|
+
skills_section += f"You have access to {len(skills)} specialized skills:\n\n"
|
|
117
|
+
|
|
118
|
+
for skill in skills:
|
|
119
|
+
skills_section += f"### 📚 {skill.name.replace('-', ' ').title()}\n\n"
|
|
120
|
+
skills_section += f"**Source:** {skill.source}\n"
|
|
121
|
+
if skill.description:
|
|
122
|
+
skills_section += f"**Description:** {skill.description}\n"
|
|
123
|
+
skills_section += "\n```\n"
|
|
124
|
+
skills_section += skill.content
|
|
125
|
+
skills_section += "\n```\n\n"
|
|
126
|
+
|
|
127
|
+
skills_section += "=" * 80 + "\n"
|
|
128
|
+
|
|
129
|
+
return base_prompt + skills_section
|
|
130
|
+
|
|
131
|
+
def list_agent_skill_mappings(self) -> Dict[str, List[str]]:
|
|
132
|
+
"""
|
|
133
|
+
Get all agent-to-skill mappings.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Dictionary mapping agent IDs to lists of skill names
|
|
137
|
+
"""
|
|
138
|
+
return self.agent_skill_mapping.copy()
|
|
139
|
+
|
|
140
|
+
def add_skill_to_agent(self, agent_type: str, skill_name: str) -> bool:
|
|
141
|
+
"""
|
|
142
|
+
Add a skill to an agent's mapping.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
agent_type: Agent type/ID
|
|
146
|
+
skill_name: Name of the skill to add
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
True if successful, False if skill not found
|
|
150
|
+
"""
|
|
151
|
+
skill = self.registry.get_skill(skill_name)
|
|
152
|
+
if not skill:
|
|
153
|
+
logger.error(f"Cannot add skill '{skill_name}': skill not found")
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
if agent_type not in self.agent_skill_mapping:
|
|
157
|
+
self.agent_skill_mapping[agent_type] = []
|
|
158
|
+
|
|
159
|
+
if skill_name not in self.agent_skill_mapping[agent_type]:
|
|
160
|
+
self.agent_skill_mapping[agent_type].append(skill_name)
|
|
161
|
+
logger.info(f"Added skill '{skill_name}' to agent '{agent_type}'")
|
|
162
|
+
|
|
163
|
+
return True
|
|
164
|
+
|
|
165
|
+
def remove_skill_from_agent(self, agent_type: str, skill_name: str) -> bool:
|
|
166
|
+
"""
|
|
167
|
+
Remove a skill from an agent's mapping.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
agent_type: Agent type/ID
|
|
171
|
+
skill_name: Name of the skill to remove
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
True if successful, False if not found
|
|
175
|
+
"""
|
|
176
|
+
if agent_type not in self.agent_skill_mapping:
|
|
177
|
+
return False
|
|
178
|
+
|
|
179
|
+
if skill_name in self.agent_skill_mapping[agent_type]:
|
|
180
|
+
self.agent_skill_mapping[agent_type].remove(skill_name)
|
|
181
|
+
logger.info(f"Removed skill '{skill_name}' from agent '{agent_type}'")
|
|
182
|
+
return True
|
|
183
|
+
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
def reload(self):
|
|
187
|
+
"""Reload skills and agent mappings."""
|
|
188
|
+
logger.info("Reloading skill manager...")
|
|
189
|
+
self.registry.reload()
|
|
190
|
+
self.agent_skill_mapping.clear()
|
|
191
|
+
self._load_agent_mappings()
|
|
192
|
+
logger.info("Skill manager reloaded")
|
|
193
|
+
|
|
194
|
+
def infer_agents_for_skill(self, skill) -> List[str]:
|
|
195
|
+
"""Infer which agents should have this skill based on tags/name.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
skill: Skill object to analyze
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
List of agent IDs that should have this skill
|
|
202
|
+
"""
|
|
203
|
+
agents = []
|
|
204
|
+
content_lower = skill.content.lower()
|
|
205
|
+
name_lower = skill.name.lower()
|
|
206
|
+
|
|
207
|
+
# Python-related
|
|
208
|
+
if any(
|
|
209
|
+
tag in content_lower or tag in name_lower
|
|
210
|
+
for tag in ["python", "django", "flask", "fastapi"]
|
|
211
|
+
):
|
|
212
|
+
agents.append("python-engineer")
|
|
213
|
+
|
|
214
|
+
# TypeScript/JavaScript-related
|
|
215
|
+
if any(
|
|
216
|
+
tag in content_lower or tag in name_lower
|
|
217
|
+
for tag in ["typescript", "javascript", "react", "next", "vue", "node"]
|
|
218
|
+
):
|
|
219
|
+
agents.extend(["typescript-engineer", "react-engineer", "nextjs-engineer"])
|
|
220
|
+
|
|
221
|
+
# Go-related
|
|
222
|
+
if any(tag in content_lower or tag in name_lower for tag in ["golang", "go "]):
|
|
223
|
+
agents.append("golang-engineer")
|
|
224
|
+
|
|
225
|
+
# Ops-related
|
|
226
|
+
if any(
|
|
227
|
+
tag in content_lower or tag in name_lower
|
|
228
|
+
for tag in ["docker", "kubernetes", "deploy", "devops", "ops"]
|
|
229
|
+
):
|
|
230
|
+
agents.extend(["ops", "devops", "local-ops"])
|
|
231
|
+
|
|
232
|
+
# Testing/QA-related
|
|
233
|
+
if any(
|
|
234
|
+
tag in content_lower or tag in name_lower
|
|
235
|
+
for tag in ["test", "qa", "quality", "assert"]
|
|
236
|
+
):
|
|
237
|
+
agents.extend(["qa", "web-qa", "api-qa"])
|
|
238
|
+
|
|
239
|
+
# Documentation-related
|
|
240
|
+
if any(
|
|
241
|
+
tag in content_lower or tag in name_lower
|
|
242
|
+
for tag in ["documentation", "docs", "api doc", "openapi"]
|
|
243
|
+
):
|
|
244
|
+
agents.extend(["docs", "documentation", "technical-writer"])
|
|
245
|
+
|
|
246
|
+
# Remove duplicates
|
|
247
|
+
return list(set(agents))
|
|
248
|
+
|
|
249
|
+
def save_mappings_to_config(self, config_path: Optional[Path] = None):
|
|
250
|
+
"""Save current agent-skill mappings to configuration file.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
config_path: Path to configuration file. If None, uses default.
|
|
254
|
+
"""
|
|
255
|
+
import json
|
|
256
|
+
|
|
257
|
+
if config_path is None:
|
|
258
|
+
config_path = Path.cwd() / ".claude-mpm" / "skills_config.json"
|
|
259
|
+
|
|
260
|
+
# Ensure directory exists
|
|
261
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
262
|
+
|
|
263
|
+
# Save mappings
|
|
264
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
|
265
|
+
json.dump(self.agent_skill_mapping, f, indent=2)
|
|
266
|
+
|
|
267
|
+
logger.info(f"Saved skill mappings to {config_path}")
|
|
268
|
+
|
|
269
|
+
def load_mappings_from_config(self, config_path: Optional[Path] = None):
|
|
270
|
+
"""Load agent-skill mappings from configuration file.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
config_path: Path to configuration file. If None, uses default.
|
|
274
|
+
"""
|
|
275
|
+
import json
|
|
276
|
+
|
|
277
|
+
if config_path is None:
|
|
278
|
+
config_path = Path.cwd() / ".claude-mpm" / "skills_config.json"
|
|
279
|
+
|
|
280
|
+
if not config_path.exists():
|
|
281
|
+
logger.debug(f"No skill mappings config found at {config_path}")
|
|
282
|
+
return
|
|
283
|
+
|
|
284
|
+
try:
|
|
285
|
+
with open(config_path, encoding="utf-8") as f:
|
|
286
|
+
loaded_mappings = json.load(f)
|
|
287
|
+
|
|
288
|
+
# Merge with existing mappings
|
|
289
|
+
for agent_id, skills in loaded_mappings.items():
|
|
290
|
+
if agent_id not in self.agent_skill_mapping:
|
|
291
|
+
self.agent_skill_mapping[agent_id] = []
|
|
292
|
+
for skill in skills:
|
|
293
|
+
if skill not in self.agent_skill_mapping[agent_id]:
|
|
294
|
+
self.agent_skill_mapping[agent_id].append(skill)
|
|
295
|
+
|
|
296
|
+
logger.info(f"Loaded skill mappings from {config_path}")
|
|
297
|
+
except Exception as e:
|
|
298
|
+
logger.error(f"Error loading skill mappings from {config_path}: {e}")
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
# Global manager instance (singleton pattern)
|
|
302
|
+
_manager: Optional[SkillManager] = None
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def get_manager() -> SkillManager:
|
|
306
|
+
"""Get the global skill manager (singleton)."""
|
|
307
|
+
global _manager
|
|
308
|
+
if _manager is None:
|
|
309
|
+
_manager = SkillManager()
|
|
310
|
+
return _manager
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"""Skills Registry - Helper class for registry operations.
|
|
2
|
+
|
|
3
|
+
This module provides a helper class for working with the skills registry YAML file.
|
|
4
|
+
It offers convenient methods for loading, querying, and validating the registry.
|
|
5
|
+
|
|
6
|
+
The skills registry (config/skills_registry.yaml) is the source of truth for:
|
|
7
|
+
- Skill-to-agent mappings
|
|
8
|
+
- Skill metadata (descriptions, categories, sources)
|
|
9
|
+
- Skill source repositories
|
|
10
|
+
- Version information
|
|
11
|
+
|
|
12
|
+
Design:
|
|
13
|
+
- Read-only registry operations (no modifications)
|
|
14
|
+
- Structured access to registry data
|
|
15
|
+
- Validation of registry structure
|
|
16
|
+
- Error handling with fallback to empty results
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any, Dict, List, Optional
|
|
21
|
+
|
|
22
|
+
import yaml
|
|
23
|
+
|
|
24
|
+
from claude_mpm.core.mixins import LoggerMixin
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SkillsRegistry(LoggerMixin):
|
|
28
|
+
"""Helper class for skills registry operations.
|
|
29
|
+
|
|
30
|
+
Provides structured access to the skills registry YAML file with
|
|
31
|
+
methods for querying agent skills, skill metadata, and validation.
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
>>> registry = SkillsRegistry()
|
|
35
|
+
>>> skills = registry.get_agent_skills('engineer')
|
|
36
|
+
>>> print(skills) # ['test-driven-development', 'systematic-debugging', ...]
|
|
37
|
+
>>>
|
|
38
|
+
>>> metadata = registry.get_skill_metadata('test-driven-development')
|
|
39
|
+
>>> print(metadata['category']) # 'testing'
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, registry_path: Optional[Path] = None) -> None:
|
|
43
|
+
"""Initialize Skills Registry.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
registry_path: Optional path to registry YAML file.
|
|
47
|
+
If None, uses default config/skills_registry.yaml
|
|
48
|
+
"""
|
|
49
|
+
super().__init__()
|
|
50
|
+
|
|
51
|
+
if registry_path is None:
|
|
52
|
+
# Default to config/skills_registry.yaml
|
|
53
|
+
registry_path = Path(__file__).parent.parent.parent.parent / "config" / "skills_registry.yaml"
|
|
54
|
+
|
|
55
|
+
self.registry_path: Path = registry_path
|
|
56
|
+
self.data: Dict[str, Any] = self.load_registry(registry_path)
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def load_registry(registry_path: Path) -> Dict[str, Any]:
|
|
60
|
+
"""Load and parse registry YAML file.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
registry_path: Path to skills_registry.yaml
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Dict containing parsed registry data, or empty dict on error
|
|
67
|
+
|
|
68
|
+
Example:
|
|
69
|
+
>>> data = SkillsRegistry.load_registry(Path('config/skills_registry.yaml'))
|
|
70
|
+
>>> print(data['version']) # '1.0.0'
|
|
71
|
+
"""
|
|
72
|
+
if not registry_path.exists():
|
|
73
|
+
return {}
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
with open(registry_path, encoding='utf-8') as f:
|
|
77
|
+
data = yaml.safe_load(f)
|
|
78
|
+
return data if data is not None else {}
|
|
79
|
+
except (OSError, yaml.YAMLError):
|
|
80
|
+
# Graceful degradation - return empty dict
|
|
81
|
+
return {}
|
|
82
|
+
|
|
83
|
+
def get_agent_skills(self, agent_id: str) -> List[str]:
|
|
84
|
+
"""Get skills for a specific agent.
|
|
85
|
+
|
|
86
|
+
Reads from registry['agent_skills'][agent_id] and combines
|
|
87
|
+
'required' and 'optional' skill lists.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
agent_id: Agent identifier (e.g., 'engineer', 'python_engineer')
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
List of skill names assigned to this agent (required + optional)
|
|
94
|
+
|
|
95
|
+
Example:
|
|
96
|
+
>>> registry = SkillsRegistry()
|
|
97
|
+
>>> skills = registry.get_agent_skills('engineer')
|
|
98
|
+
>>> print(skills)
|
|
99
|
+
['test-driven-development', 'systematic-debugging', 'code-review', 'git-worktrees']
|
|
100
|
+
"""
|
|
101
|
+
agent_skills = self.data.get('agent_skills', {}).get(agent_id, {})
|
|
102
|
+
|
|
103
|
+
required = agent_skills.get('required', [])
|
|
104
|
+
optional = agent_skills.get('optional', [])
|
|
105
|
+
|
|
106
|
+
return required + optional
|
|
107
|
+
|
|
108
|
+
def get_skill_metadata(self, skill_name: str) -> Dict[str, Any]:
|
|
109
|
+
"""Get metadata for a specific skill.
|
|
110
|
+
|
|
111
|
+
Retrieves skill information from registry['skills_metadata'][skill_name].
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
skill_name: Skill identifier (e.g., 'test-driven-development')
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Dict containing skill metadata:
|
|
118
|
+
- category: Skill category
|
|
119
|
+
- source: Source repository name
|
|
120
|
+
- url: Source URL
|
|
121
|
+
- description: Brief description
|
|
122
|
+
|
|
123
|
+
Example:
|
|
124
|
+
>>> registry = SkillsRegistry()
|
|
125
|
+
>>> metadata = registry.get_skill_metadata('test-driven-development')
|
|
126
|
+
>>> print(f"{metadata['category']}: {metadata['description']}")
|
|
127
|
+
testing: Enforces RED/GREEN/REFACTOR TDD cycle
|
|
128
|
+
"""
|
|
129
|
+
return self.data.get('skills_metadata', {}).get(skill_name, {})
|
|
130
|
+
|
|
131
|
+
def list_all_skills(self) -> List[str]:
|
|
132
|
+
"""List all skills in the registry.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
List of all skill names defined in skills_metadata
|
|
136
|
+
|
|
137
|
+
Example:
|
|
138
|
+
>>> registry = SkillsRegistry()
|
|
139
|
+
>>> all_skills = registry.list_all_skills()
|
|
140
|
+
>>> print(f"Total skills: {len(all_skills)}")
|
|
141
|
+
Total skills: 15
|
|
142
|
+
"""
|
|
143
|
+
return list(self.data.get('skills_metadata', {}).keys())
|
|
144
|
+
|
|
145
|
+
def list_all_agents(self) -> List[str]:
|
|
146
|
+
"""List all agents in the registry.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
List of all agent IDs with skill assignments
|
|
150
|
+
|
|
151
|
+
Example:
|
|
152
|
+
>>> registry = SkillsRegistry()
|
|
153
|
+
>>> agents = registry.list_all_agents()
|
|
154
|
+
>>> print(f"Agents with skills: {len(agents)}")
|
|
155
|
+
"""
|
|
156
|
+
return list(self.data.get('agent_skills', {}).keys())
|
|
157
|
+
|
|
158
|
+
def get_skills_by_category(self, category: str) -> List[str]:
|
|
159
|
+
"""Get all skills in a specific category.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
category: Category name (e.g., 'testing', 'debugging', 'development')
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
List of skill names in this category
|
|
166
|
+
|
|
167
|
+
Example:
|
|
168
|
+
>>> registry = SkillsRegistry()
|
|
169
|
+
>>> testing_skills = registry.get_skills_by_category('testing')
|
|
170
|
+
>>> print(testing_skills)
|
|
171
|
+
['test-driven-development', 'webapp-testing', 'async-testing', ...]
|
|
172
|
+
"""
|
|
173
|
+
skills = []
|
|
174
|
+
for skill_name, metadata in self.data.get('skills_metadata', {}).items():
|
|
175
|
+
if metadata.get('category') == category:
|
|
176
|
+
skills.append(skill_name)
|
|
177
|
+
|
|
178
|
+
return skills
|
|
179
|
+
|
|
180
|
+
def get_skills_by_source(self, source: str) -> List[str]:
|
|
181
|
+
"""Get all skills from a specific source repository.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
source: Source repository name (e.g., 'superpowers', 'anthropic', 'community')
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
List of skill names from this source
|
|
188
|
+
|
|
189
|
+
Example:
|
|
190
|
+
>>> registry = SkillsRegistry()
|
|
191
|
+
>>> superpowers_skills = registry.get_skills_by_source('superpowers')
|
|
192
|
+
>>> print(f"Skills from superpowers: {len(superpowers_skills)}")
|
|
193
|
+
"""
|
|
194
|
+
skills = []
|
|
195
|
+
for skill_name, metadata in self.data.get('skills_metadata', {}).items():
|
|
196
|
+
if metadata.get('source') == source:
|
|
197
|
+
skills.append(skill_name)
|
|
198
|
+
|
|
199
|
+
return skills
|
|
200
|
+
|
|
201
|
+
def validate_registry(self) -> Dict[str, Any]:
|
|
202
|
+
"""Validate registry structure and content.
|
|
203
|
+
|
|
204
|
+
Checks:
|
|
205
|
+
- Required top-level keys present
|
|
206
|
+
- Version format valid
|
|
207
|
+
- Agent skills references valid
|
|
208
|
+
- Skill metadata complete
|
|
209
|
+
- No orphaned references
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Dict containing:
|
|
213
|
+
- valid: True if all checks pass
|
|
214
|
+
- errors: List of error messages
|
|
215
|
+
- warnings: List of warning messages
|
|
216
|
+
|
|
217
|
+
Example:
|
|
218
|
+
>>> registry = SkillsRegistry()
|
|
219
|
+
>>> result = registry.validate_registry()
|
|
220
|
+
>>> if not result['valid']:
|
|
221
|
+
... print(f"Registry errors: {result['errors']}")
|
|
222
|
+
"""
|
|
223
|
+
errors = []
|
|
224
|
+
warnings = []
|
|
225
|
+
|
|
226
|
+
# Check required top-level keys
|
|
227
|
+
required_keys = ['version', 'last_updated', 'agent_skills', 'skills_metadata']
|
|
228
|
+
for key in required_keys:
|
|
229
|
+
if key not in self.data:
|
|
230
|
+
errors.append(f"Missing required key: {key}")
|
|
231
|
+
|
|
232
|
+
# Validate version format
|
|
233
|
+
version = self.data.get('version', '')
|
|
234
|
+
if not version or not isinstance(version, str):
|
|
235
|
+
errors.append("Invalid or missing version field")
|
|
236
|
+
|
|
237
|
+
# Check agent skill references
|
|
238
|
+
agent_skills = self.data.get('agent_skills', {})
|
|
239
|
+
skills_metadata = self.data.get('skills_metadata', {})
|
|
240
|
+
|
|
241
|
+
for agent_id, agent_data in agent_skills.items():
|
|
242
|
+
# Validate structure
|
|
243
|
+
if not isinstance(agent_data, dict):
|
|
244
|
+
errors.append(f"Agent '{agent_id}' has invalid structure")
|
|
245
|
+
continue
|
|
246
|
+
|
|
247
|
+
# Check skill references
|
|
248
|
+
all_skills = agent_data.get('required', []) + agent_data.get('optional', [])
|
|
249
|
+
|
|
250
|
+
for skill in all_skills:
|
|
251
|
+
if skill not in skills_metadata:
|
|
252
|
+
warnings.append(
|
|
253
|
+
f"Agent '{agent_id}' references undefined skill: {skill}"
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Check for orphaned skills (in metadata but not assigned to any agent)
|
|
257
|
+
assigned_skills = set()
|
|
258
|
+
for agent_data in agent_skills.values():
|
|
259
|
+
assigned_skills.update(agent_data.get('required', []))
|
|
260
|
+
assigned_skills.update(agent_data.get('optional', []))
|
|
261
|
+
|
|
262
|
+
for skill_name in skills_metadata:
|
|
263
|
+
if skill_name not in assigned_skills:
|
|
264
|
+
warnings.append(f"Skill '{skill_name}' not assigned to any agent")
|
|
265
|
+
|
|
266
|
+
# Validate skill metadata completeness
|
|
267
|
+
required_metadata_fields = ['category', 'source', 'description']
|
|
268
|
+
for skill_name, metadata in skills_metadata.items():
|
|
269
|
+
for field in required_metadata_fields:
|
|
270
|
+
if field not in metadata or not metadata[field]:
|
|
271
|
+
warnings.append(
|
|
272
|
+
f"Skill '{skill_name}' missing {field} in metadata"
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
'valid': len(errors) == 0,
|
|
277
|
+
'errors': errors,
|
|
278
|
+
'warnings': warnings
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
def get_registry_info(self) -> Dict[str, Any]:
|
|
282
|
+
"""Get summary information about the registry.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
Dict containing:
|
|
286
|
+
- version: Registry version
|
|
287
|
+
- last_updated: Last update timestamp
|
|
288
|
+
- total_skills: Count of skills
|
|
289
|
+
- total_agents: Count of agents with skills
|
|
290
|
+
- categories: List of skill categories
|
|
291
|
+
- sources: List of skill sources
|
|
292
|
+
|
|
293
|
+
Example:
|
|
294
|
+
>>> registry = SkillsRegistry()
|
|
295
|
+
>>> info = registry.get_registry_info()
|
|
296
|
+
>>> print(f"Registry v{info['version']}")
|
|
297
|
+
>>> print(f"Total skills: {info['total_skills']}")
|
|
298
|
+
"""
|
|
299
|
+
skills_metadata = self.data.get('skills_metadata', {})
|
|
300
|
+
agent_skills = self.data.get('agent_skills', {})
|
|
301
|
+
|
|
302
|
+
# Get unique categories
|
|
303
|
+
categories = set()
|
|
304
|
+
for metadata in skills_metadata.values():
|
|
305
|
+
if 'category' in metadata:
|
|
306
|
+
categories.add(metadata['category'])
|
|
307
|
+
|
|
308
|
+
# Get unique sources
|
|
309
|
+
sources = set()
|
|
310
|
+
for metadata in skills_metadata.values():
|
|
311
|
+
if 'source' in metadata:
|
|
312
|
+
sources.add(metadata['source'])
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
'version': self.data.get('version', 'unknown'),
|
|
316
|
+
'last_updated': self.data.get('last_updated', 'unknown'),
|
|
317
|
+
'total_skills': len(skills_metadata),
|
|
318
|
+
'total_agents': len(agent_skills),
|
|
319
|
+
'categories': sorted(list(categories)),
|
|
320
|
+
'sources': sorted(list(sources))
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
def search_skills(self, query: str) -> List[Dict[str, Any]]:
|
|
324
|
+
"""Search skills by name or description.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
query: Search query string
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
List of matching skills with their metadata
|
|
331
|
+
|
|
332
|
+
Example:
|
|
333
|
+
>>> registry = SkillsRegistry()
|
|
334
|
+
>>> results = registry.search_skills('debug')
|
|
335
|
+
>>> for skill in results:
|
|
336
|
+
... print(f"{skill['name']}: {skill['description']}")
|
|
337
|
+
"""
|
|
338
|
+
query_lower = query.lower()
|
|
339
|
+
results = []
|
|
340
|
+
|
|
341
|
+
for skill_name, metadata in self.data.get('skills_metadata', {}).items():
|
|
342
|
+
# Search in name and description
|
|
343
|
+
if (query_lower in skill_name.lower() or
|
|
344
|
+
query_lower in metadata.get('description', '').lower()):
|
|
345
|
+
|
|
346
|
+
results.append({
|
|
347
|
+
'name': skill_name,
|
|
348
|
+
**metadata
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
return results
|