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.
Files changed (203) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_ENGINEER.md +286 -0
  3. claude_mpm/agents/BASE_PM.md +255 -23
  4. claude_mpm/agents/PM_INSTRUCTIONS.md +40 -0
  5. claude_mpm/agents/agent_loader.py +4 -4
  6. claude_mpm/agents/templates/agentic-coder-optimizer.json +9 -2
  7. claude_mpm/agents/templates/api_qa.json +7 -1
  8. claude_mpm/agents/templates/clerk-ops.json +8 -1
  9. claude_mpm/agents/templates/code_analyzer.json +4 -1
  10. claude_mpm/agents/templates/dart_engineer.json +11 -1
  11. claude_mpm/agents/templates/data_engineer.json +11 -1
  12. claude_mpm/agents/templates/documentation.json +6 -1
  13. claude_mpm/agents/templates/engineer.json +18 -1
  14. claude_mpm/agents/templates/gcp_ops_agent.json +8 -1
  15. claude_mpm/agents/templates/golang_engineer.json +11 -1
  16. claude_mpm/agents/templates/java_engineer.json +12 -2
  17. claude_mpm/agents/templates/local_ops_agent.json +216 -37
  18. claude_mpm/agents/templates/nextjs_engineer.json +11 -1
  19. claude_mpm/agents/templates/ops.json +8 -1
  20. claude_mpm/agents/templates/php-engineer.json +11 -1
  21. claude_mpm/agents/templates/project_organizer.json +9 -2
  22. claude_mpm/agents/templates/prompt-engineer.json +5 -1
  23. claude_mpm/agents/templates/python_engineer.json +19 -4
  24. claude_mpm/agents/templates/qa.json +7 -1
  25. claude_mpm/agents/templates/react_engineer.json +11 -1
  26. claude_mpm/agents/templates/refactoring_engineer.json +8 -1
  27. claude_mpm/agents/templates/research.json +4 -1
  28. claude_mpm/agents/templates/ruby-engineer.json +11 -1
  29. claude_mpm/agents/templates/rust_engineer.json +23 -8
  30. claude_mpm/agents/templates/security.json +6 -1
  31. claude_mpm/agents/templates/svelte-engineer.json +225 -0
  32. claude_mpm/agents/templates/ticketing.json +6 -1
  33. claude_mpm/agents/templates/typescript_engineer.json +11 -1
  34. claude_mpm/agents/templates/vercel_ops_agent.json +8 -1
  35. claude_mpm/agents/templates/version_control.json +8 -1
  36. claude_mpm/agents/templates/web_qa.json +7 -1
  37. claude_mpm/agents/templates/web_ui.json +11 -1
  38. claude_mpm/cli/commands/__init__.py +2 -0
  39. claude_mpm/cli/commands/configure.py +164 -16
  40. claude_mpm/cli/commands/configure_agent_display.py +6 -6
  41. claude_mpm/cli/commands/configure_behavior_manager.py +8 -8
  42. claude_mpm/cli/commands/configure_navigation.py +20 -18
  43. claude_mpm/cli/commands/configure_startup_manager.py +14 -14
  44. claude_mpm/cli/commands/configure_template_editor.py +8 -8
  45. claude_mpm/cli/commands/mpm_init.py +109 -24
  46. claude_mpm/cli/commands/skills.py +434 -0
  47. claude_mpm/cli/executor.py +2 -0
  48. claude_mpm/cli/interactive/__init__.py +3 -0
  49. claude_mpm/cli/interactive/skills_wizard.py +491 -0
  50. claude_mpm/cli/parsers/base_parser.py +7 -0
  51. claude_mpm/cli/parsers/skills_parser.py +137 -0
  52. claude_mpm/cli/startup.py +83 -0
  53. claude_mpm/commands/mpm-auto-configure.md +52 -0
  54. claude_mpm/commands/mpm-help.md +3 -0
  55. claude_mpm/commands/mpm-init.md +112 -6
  56. claude_mpm/commands/mpm-version.md +113 -0
  57. claude_mpm/commands/mpm.md +1 -0
  58. claude_mpm/config/agent_config.py +2 -2
  59. claude_mpm/constants.py +12 -0
  60. claude_mpm/core/config.py +42 -0
  61. claude_mpm/core/enums.py +18 -0
  62. claude_mpm/core/factories.py +1 -1
  63. claude_mpm/core/optimized_agent_loader.py +3 -3
  64. claude_mpm/core/types.py +2 -9
  65. claude_mpm/dashboard/static/js/dashboard.js +0 -14
  66. claude_mpm/dashboard/templates/index.html +3 -41
  67. claude_mpm/hooks/__init__.py +8 -0
  68. claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
  69. claude_mpm/hooks/session_resume_hook.py +121 -0
  70. claude_mpm/models/resume_log.py +340 -0
  71. claude_mpm/services/agents/auto_config_manager.py +1 -1
  72. claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
  73. claude_mpm/services/agents/deployment/agent_record_service.py +1 -1
  74. claude_mpm/services/agents/deployment/agent_validator.py +17 -1
  75. claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
  76. claude_mpm/services/agents/deployment/local_template_deployment.py +1 -1
  77. claude_mpm/services/agents/deployment/validation/__init__.py +3 -1
  78. claude_mpm/services/agents/deployment/validation/validation_result.py +1 -9
  79. claude_mpm/services/agents/local_template_manager.py +1 -1
  80. claude_mpm/services/agents/recommender.py +47 -0
  81. claude_mpm/services/cli/resume_service.py +617 -0
  82. claude_mpm/services/cli/session_manager.py +87 -0
  83. claude_mpm/services/cli/session_resume_helper.py +352 -0
  84. claude_mpm/services/core/models/health.py +1 -28
  85. claude_mpm/services/core/path_resolver.py +1 -1
  86. claude_mpm/services/infrastructure/monitoring/__init__.py +1 -1
  87. claude_mpm/services/infrastructure/monitoring/aggregator.py +12 -12
  88. claude_mpm/services/infrastructure/monitoring/base.py +5 -13
  89. claude_mpm/services/infrastructure/monitoring/network.py +7 -6
  90. claude_mpm/services/infrastructure/monitoring/process.py +13 -12
  91. claude_mpm/services/infrastructure/monitoring/resources.py +7 -6
  92. claude_mpm/services/infrastructure/monitoring/service.py +16 -15
  93. claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
  94. claude_mpm/services/local_ops/__init__.py +1 -1
  95. claude_mpm/services/local_ops/crash_detector.py +1 -1
  96. claude_mpm/services/local_ops/health_checks/http_check.py +2 -1
  97. claude_mpm/services/local_ops/health_checks/process_check.py +2 -1
  98. claude_mpm/services/local_ops/health_checks/resource_check.py +2 -1
  99. claude_mpm/services/local_ops/health_manager.py +1 -1
  100. claude_mpm/services/local_ops/restart_manager.py +1 -1
  101. claude_mpm/services/mcp_config_manager.py +7 -131
  102. claude_mpm/services/session_manager.py +205 -1
  103. claude_mpm/services/shared/async_service_base.py +16 -27
  104. claude_mpm/services/shared/lifecycle_service_base.py +1 -14
  105. claude_mpm/services/socketio/handlers/__init__.py +5 -2
  106. claude_mpm/services/socketio/handlers/hook.py +10 -0
  107. claude_mpm/services/socketio/handlers/registry.py +4 -2
  108. claude_mpm/services/socketio/server/main.py +7 -7
  109. claude_mpm/services/unified/deployment_strategies/local.py +1 -1
  110. claude_mpm/services/version_service.py +104 -1
  111. claude_mpm/skills/__init__.py +42 -0
  112. claude_mpm/skills/agent_skills_injector.py +331 -0
  113. claude_mpm/skills/bundled/LICENSE_ATTRIBUTIONS.md +79 -0
  114. claude_mpm/skills/bundled/__init__.py +6 -0
  115. claude_mpm/skills/bundled/api-documentation.md +393 -0
  116. claude_mpm/skills/bundled/async-testing.md +571 -0
  117. claude_mpm/skills/bundled/code-review.md +143 -0
  118. claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +75 -0
  119. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +184 -0
  120. claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +107 -0
  121. claude_mpm/skills/bundled/collaboration/requesting-code-review/code-reviewer.md +146 -0
  122. claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +118 -0
  123. claude_mpm/skills/bundled/database-migration.md +199 -0
  124. claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +177 -0
  125. claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
  126. claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
  127. claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
  128. claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
  129. claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
  130. claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
  131. claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
  132. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
  133. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
  134. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
  135. claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +175 -0
  136. claude_mpm/skills/bundled/debugging/verification-before-completion/references/common-failures.md +213 -0
  137. claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +314 -0
  138. claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +227 -0
  139. claude_mpm/skills/bundled/docker-containerization.md +194 -0
  140. claude_mpm/skills/bundled/express-local-dev.md +1429 -0
  141. claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
  142. claude_mpm/skills/bundled/git-workflow.md +414 -0
  143. claude_mpm/skills/bundled/imagemagick.md +204 -0
  144. claude_mpm/skills/bundled/json-data-handling.md +223 -0
  145. claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +74 -0
  146. claude_mpm/skills/bundled/main/internal-comms/SKILL.md +32 -0
  147. claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
  148. claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
  149. claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
  150. claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
  151. claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +328 -0
  152. claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
  153. claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
  154. claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
  155. claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
  156. claude_mpm/skills/bundled/main/mcp-builder/scripts/connections.py +150 -0
  157. claude_mpm/skills/bundled/main/mcp-builder/scripts/evaluation.py +372 -0
  158. claude_mpm/skills/bundled/main/skill-creator/SKILL.md +209 -0
  159. claude_mpm/skills/bundled/main/skill-creator/scripts/init_skill.py +302 -0
  160. claude_mpm/skills/bundled/main/skill-creator/scripts/package_skill.py +111 -0
  161. claude_mpm/skills/bundled/main/skill-creator/scripts/quick_validate.py +65 -0
  162. claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
  163. claude_mpm/skills/bundled/pdf.md +141 -0
  164. claude_mpm/skills/bundled/performance-profiling.md +567 -0
  165. claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
  166. claude_mpm/skills/bundled/security-scanning.md +327 -0
  167. claude_mpm/skills/bundled/systematic-debugging.md +473 -0
  168. claude_mpm/skills/bundled/test-driven-development.md +378 -0
  169. claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +123 -0
  170. claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
  171. claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
  172. claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
  173. claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
  174. claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
  175. claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
  176. claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +304 -0
  177. claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +96 -0
  178. claude_mpm/skills/bundled/testing/webapp-testing/examples/console_logging.py +35 -0
  179. claude_mpm/skills/bundled/testing/webapp-testing/examples/element_discovery.py +40 -0
  180. claude_mpm/skills/bundled/testing/webapp-testing/examples/static_html_automation.py +34 -0
  181. claude_mpm/skills/bundled/testing/webapp-testing/scripts/with_server.py +107 -0
  182. claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
  183. claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
  184. claude_mpm/skills/bundled/xlsx.md +157 -0
  185. claude_mpm/skills/registry.py +286 -0
  186. claude_mpm/skills/skill_manager.py +310 -0
  187. claude_mpm/skills/skills_registry.py +351 -0
  188. claude_mpm/skills/skills_service.py +730 -0
  189. claude_mpm/utils/agent_dependency_loader.py +2 -2
  190. {claude_mpm-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/METADATA +211 -33
  191. {claude_mpm-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/RECORD +195 -115
  192. claude_mpm/agents/INSTRUCTIONS_OLD_DEPRECATED.md +0 -602
  193. claude_mpm/dashboard/static/css/code-tree.css +0 -1639
  194. claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +0 -353
  195. claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +0 -235
  196. claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +0 -409
  197. claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +0 -435
  198. claude_mpm/dashboard/static/js/components/code-tree.js +0 -5869
  199. claude_mpm/dashboard/static/js/components/code-viewer.js +0 -1386
  200. {claude_mpm-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/WHEEL +0 -0
  201. {claude_mpm-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/entry_points.txt +0 -0
  202. {claude_mpm-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/licenses/LICENSE +0 -0
  203. {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