claude-mpm 4.15.3__py3-none-any.whl → 4.16.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/VERSION +1 -1
- 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 +13 -0
- 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 +11 -1
- 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 +11 -1
- claude_mpm/agents/templates/security.json +6 -1
- 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/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/interactive/__init__.py +3 -0
- claude_mpm/cli/interactive/skills_wizard.py +491 -0
- claude_mpm/cli/startup.py +26 -0
- claude_mpm/dashboard/static/js/dashboard.js +0 -14
- claude_mpm/dashboard/templates/index.html +3 -41
- 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/skills/__init__.py +21 -0
- claude_mpm/skills/bundled/__init__.py +6 -0
- claude_mpm/skills/registry.py +198 -0
- claude_mpm/skills/skill_manager.py +310 -0
- {claude_mpm-4.15.3.dist-info → claude_mpm-4.16.0.dist-info}/METADATA +37 -8
- {claude_mpm-4.15.3.dist-info → claude_mpm-4.16.0.dist-info}/RECORD +57 -72
- 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/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
- {claude_mpm-4.15.3.dist-info → claude_mpm-4.16.0.dist-info}/WHEEL +0 -0
- {claude_mpm-4.15.3.dist-info → claude_mpm-4.16.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.15.3.dist-info → claude_mpm-4.16.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.15.3.dist-info → claude_mpm-4.16.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
"""Interactive Skills Selection Wizard for Claude MPM.
|
|
2
|
+
|
|
3
|
+
This module provides a step-by-step interactive wizard for selecting and configuring
|
|
4
|
+
skills for agents with user-friendly prompts and intelligent auto-linking.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, List, Optional, Tuple
|
|
8
|
+
|
|
9
|
+
from claude_mpm.core.logging_config import get_logger
|
|
10
|
+
from claude_mpm.skills.registry import get_registry
|
|
11
|
+
from claude_mpm.skills.skill_manager import get_manager
|
|
12
|
+
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Agent-to-skills auto-linking mappings
|
|
17
|
+
ENGINEER_CORE_SKILLS = [
|
|
18
|
+
"test-driven-development",
|
|
19
|
+
"systematic-debugging",
|
|
20
|
+
"code-review",
|
|
21
|
+
"refactoring-patterns",
|
|
22
|
+
"git-workflow",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
PYTHON_SKILLS = ENGINEER_CORE_SKILLS + ["async-testing"]
|
|
26
|
+
TYPESCRIPT_SKILLS = ENGINEER_CORE_SKILLS + ["async-testing"]
|
|
27
|
+
GOLANG_SKILLS = ENGINEER_CORE_SKILLS + ["async-testing"]
|
|
28
|
+
REACT_SKILLS = TYPESCRIPT_SKILLS + ["performance-profiling"]
|
|
29
|
+
NEXTJS_SKILLS = REACT_SKILLS
|
|
30
|
+
VUE_SKILLS = TYPESCRIPT_SKILLS + ["performance-profiling"]
|
|
31
|
+
|
|
32
|
+
OPS_SKILLS = [
|
|
33
|
+
"docker-containerization",
|
|
34
|
+
"database-migration",
|
|
35
|
+
"security-scanning",
|
|
36
|
+
"systematic-debugging",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
DOCUMENTATION_SKILLS = [
|
|
40
|
+
"api-documentation",
|
|
41
|
+
"code-review",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
QA_SKILLS = [
|
|
45
|
+
"test-driven-development",
|
|
46
|
+
"systematic-debugging",
|
|
47
|
+
"async-testing",
|
|
48
|
+
"performance-profiling",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
# Mapping of agent types to their recommended skills
|
|
52
|
+
AGENT_SKILL_MAPPING = {
|
|
53
|
+
# Engineer agents
|
|
54
|
+
"engineer": ENGINEER_CORE_SKILLS,
|
|
55
|
+
"python-engineer": PYTHON_SKILLS,
|
|
56
|
+
"typescript-engineer": TYPESCRIPT_SKILLS,
|
|
57
|
+
"golang-engineer": GOLANG_SKILLS,
|
|
58
|
+
"react-engineer": REACT_SKILLS,
|
|
59
|
+
"nextjs-engineer": NEXTJS_SKILLS,
|
|
60
|
+
"vue-engineer": VUE_SKILLS,
|
|
61
|
+
# Ops agents
|
|
62
|
+
"ops": OPS_SKILLS,
|
|
63
|
+
"devops": OPS_SKILLS,
|
|
64
|
+
"local-ops": OPS_SKILLS,
|
|
65
|
+
# Documentation agents
|
|
66
|
+
"docs": DOCUMENTATION_SKILLS,
|
|
67
|
+
"documentation": DOCUMENTATION_SKILLS,
|
|
68
|
+
"technical-writer": DOCUMENTATION_SKILLS,
|
|
69
|
+
# QA agents
|
|
70
|
+
"qa": QA_SKILLS,
|
|
71
|
+
"web-qa": QA_SKILLS,
|
|
72
|
+
"api-qa": QA_SKILLS,
|
|
73
|
+
"tester": QA_SKILLS,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class SkillsWizard:
|
|
78
|
+
"""Interactive wizard for skills selection and configuration."""
|
|
79
|
+
|
|
80
|
+
def __init__(self):
|
|
81
|
+
"""Initialize the skills wizard."""
|
|
82
|
+
self.registry = get_registry()
|
|
83
|
+
self.manager = get_manager()
|
|
84
|
+
self.logger = logger
|
|
85
|
+
|
|
86
|
+
def run_interactive_selection(
|
|
87
|
+
self, selected_agents: Optional[List[str]] = None
|
|
88
|
+
) -> Tuple[bool, Dict[str, List[str]]]:
|
|
89
|
+
"""Run interactive skills selection wizard.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
selected_agents: List of agent IDs that were selected in agent wizard
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Tuple of (success, agent_skills_mapping)
|
|
96
|
+
- success: Boolean indicating if selection was successful
|
|
97
|
+
- agent_skills_mapping: Dict mapping agent IDs to lists of skill names
|
|
98
|
+
"""
|
|
99
|
+
try:
|
|
100
|
+
print("\n" + "=" * 60)
|
|
101
|
+
print("🎯 Skills Selection Wizard")
|
|
102
|
+
print("=" * 60)
|
|
103
|
+
print("\nI'll help you select skills for your agents.")
|
|
104
|
+
print("Press Ctrl+C anytime to cancel.\n")
|
|
105
|
+
|
|
106
|
+
# Auto-link skills based on selected agents
|
|
107
|
+
agent_skills_mapping = {}
|
|
108
|
+
if selected_agents:
|
|
109
|
+
print("📋 Auto-linking skills based on selected agents...\n")
|
|
110
|
+
agent_skills_mapping = self._auto_link_skills(selected_agents)
|
|
111
|
+
self._display_auto_linked_skills(agent_skills_mapping)
|
|
112
|
+
|
|
113
|
+
# Ask if user wants to customize
|
|
114
|
+
customize = (
|
|
115
|
+
input("\nWould you like to customize skill selections? [y/N]: ")
|
|
116
|
+
.strip()
|
|
117
|
+
.lower()
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
if customize in ["y", "yes"]:
|
|
121
|
+
agent_skills_mapping = self._run_custom_selection(
|
|
122
|
+
selected_agents, agent_skills_mapping
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Preview final configuration
|
|
126
|
+
self._preview_final_configuration(agent_skills_mapping)
|
|
127
|
+
|
|
128
|
+
# Confirm
|
|
129
|
+
confirm = (
|
|
130
|
+
input("\nApply this skills configuration? [Y/n]: ").strip().lower()
|
|
131
|
+
)
|
|
132
|
+
if confirm in ["n", "no"]:
|
|
133
|
+
return False, {}
|
|
134
|
+
|
|
135
|
+
# Apply configuration
|
|
136
|
+
self._apply_skills_configuration(agent_skills_mapping)
|
|
137
|
+
|
|
138
|
+
print("\n✅ Skills configuration complete!")
|
|
139
|
+
return True, agent_skills_mapping
|
|
140
|
+
|
|
141
|
+
except KeyboardInterrupt:
|
|
142
|
+
print("\n\n❌ Skills selection cancelled")
|
|
143
|
+
return False, {}
|
|
144
|
+
except Exception as e:
|
|
145
|
+
error_msg = f"Skills selection error: {e}"
|
|
146
|
+
self.logger.error(error_msg, exc_info=True)
|
|
147
|
+
print(f"\n❌ {error_msg}")
|
|
148
|
+
return False, {}
|
|
149
|
+
|
|
150
|
+
def _auto_link_skills(self, agent_ids: List[str]) -> Dict[str, List[str]]:
|
|
151
|
+
"""Auto-link skills to agents based on agent types.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
agent_ids: List of agent IDs
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Dictionary mapping agent IDs to skill names
|
|
158
|
+
"""
|
|
159
|
+
mapping = {}
|
|
160
|
+
for agent_id in agent_ids:
|
|
161
|
+
# Try to match against known patterns
|
|
162
|
+
skills = self._get_recommended_skills_for_agent(agent_id)
|
|
163
|
+
if skills:
|
|
164
|
+
mapping[agent_id] = skills
|
|
165
|
+
else:
|
|
166
|
+
# Default to core engineer skills if no match
|
|
167
|
+
mapping[agent_id] = ENGINEER_CORE_SKILLS.copy()
|
|
168
|
+
|
|
169
|
+
return mapping
|
|
170
|
+
|
|
171
|
+
def _get_recommended_skills_for_agent(self, agent_id: str) -> List[str]:
|
|
172
|
+
"""Get recommended skills for an agent based on its ID.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
agent_id: Agent identifier
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
List of recommended skill names
|
|
179
|
+
"""
|
|
180
|
+
agent_id_lower = agent_id.lower()
|
|
181
|
+
|
|
182
|
+
# Direct match
|
|
183
|
+
if agent_id_lower in AGENT_SKILL_MAPPING:
|
|
184
|
+
return AGENT_SKILL_MAPPING[agent_id_lower].copy()
|
|
185
|
+
|
|
186
|
+
# Fuzzy matching for common patterns
|
|
187
|
+
if "python" in agent_id_lower:
|
|
188
|
+
return PYTHON_SKILLS.copy()
|
|
189
|
+
if any(js in agent_id_lower for js in ["typescript", "ts", "javascript", "js"]):
|
|
190
|
+
return TYPESCRIPT_SKILLS.copy()
|
|
191
|
+
if "react" in agent_id_lower:
|
|
192
|
+
return REACT_SKILLS.copy()
|
|
193
|
+
if "next" in agent_id_lower:
|
|
194
|
+
return NEXTJS_SKILLS.copy()
|
|
195
|
+
if "vue" in agent_id_lower:
|
|
196
|
+
return VUE_SKILLS.copy()
|
|
197
|
+
if "go" in agent_id_lower or "golang" in agent_id_lower:
|
|
198
|
+
return GOLANG_SKILLS.copy()
|
|
199
|
+
if any(ops in agent_id_lower for ops in ["ops", "devops", "deploy"]):
|
|
200
|
+
return OPS_SKILLS.copy()
|
|
201
|
+
if any(qa in agent_id_lower for qa in ["qa", "test", "quality"]):
|
|
202
|
+
return QA_SKILLS.copy()
|
|
203
|
+
if any(doc in agent_id_lower for doc in ["doc", "writer", "technical"]):
|
|
204
|
+
return DOCUMENTATION_SKILLS.copy()
|
|
205
|
+
if "engineer" in agent_id_lower:
|
|
206
|
+
return ENGINEER_CORE_SKILLS.copy()
|
|
207
|
+
|
|
208
|
+
# Default
|
|
209
|
+
return []
|
|
210
|
+
|
|
211
|
+
def _display_auto_linked_skills(self, mapping: Dict[str, List[str]]):
|
|
212
|
+
"""Display auto-linked skills configuration.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
mapping: Agent-to-skills mapping
|
|
216
|
+
"""
|
|
217
|
+
for agent_id, skills in mapping.items():
|
|
218
|
+
print(f" • {agent_id}:")
|
|
219
|
+
for skill in skills:
|
|
220
|
+
print(f" - {skill}")
|
|
221
|
+
print()
|
|
222
|
+
|
|
223
|
+
def _run_custom_selection(
|
|
224
|
+
self, agent_ids: Optional[List[str]], initial_mapping: Dict[str, List[str]]
|
|
225
|
+
) -> Dict[str, List[str]]:
|
|
226
|
+
"""Run custom skills selection for each agent.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
agent_ids: List of agent IDs
|
|
230
|
+
initial_mapping: Initial auto-linked mapping
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Updated agent-to-skills mapping
|
|
234
|
+
"""
|
|
235
|
+
mapping = initial_mapping.copy()
|
|
236
|
+
|
|
237
|
+
# Get all available bundled skills
|
|
238
|
+
bundled_skills = self.registry.list_skills(source="bundled")
|
|
239
|
+
skill_list = sorted([skill.name for skill in bundled_skills])
|
|
240
|
+
|
|
241
|
+
print("\n" + "=" * 60)
|
|
242
|
+
print("Available Bundled Skills:")
|
|
243
|
+
print("=" * 60)
|
|
244
|
+
for i, skill in enumerate(bundled_skills, 1):
|
|
245
|
+
description = (
|
|
246
|
+
skill.description[:60] + "..."
|
|
247
|
+
if len(skill.description) > 60
|
|
248
|
+
else skill.description
|
|
249
|
+
)
|
|
250
|
+
print(f" [{i:2d}] {skill.name}")
|
|
251
|
+
print(f" {description}")
|
|
252
|
+
print()
|
|
253
|
+
|
|
254
|
+
# If no agents provided, ask which agents to configure
|
|
255
|
+
if not agent_ids:
|
|
256
|
+
agent_ids = self._get_agents_to_configure()
|
|
257
|
+
|
|
258
|
+
# Configure each agent
|
|
259
|
+
for agent_id in agent_ids:
|
|
260
|
+
print(f"\n🔧 Configuring skills for: {agent_id}")
|
|
261
|
+
current_skills = mapping.get(agent_id, [])
|
|
262
|
+
|
|
263
|
+
print(f" Current skills ({len(current_skills)}):")
|
|
264
|
+
for skill in current_skills:
|
|
265
|
+
print(f" - {skill}")
|
|
266
|
+
|
|
267
|
+
modify = (
|
|
268
|
+
input(f"\n Modify skills for {agent_id}? [y/N]: ").strip().lower()
|
|
269
|
+
)
|
|
270
|
+
if modify not in ["y", "yes"]:
|
|
271
|
+
continue
|
|
272
|
+
|
|
273
|
+
# Let user select skills
|
|
274
|
+
print("\n Enter skill numbers (comma-separated), or:")
|
|
275
|
+
print(" 'all' - Select all skills")
|
|
276
|
+
print(" 'none' - Clear all skills")
|
|
277
|
+
print(" 'keep' - Keep current selection")
|
|
278
|
+
selection = input(" Selection: ").strip().lower()
|
|
279
|
+
|
|
280
|
+
if selection == "keep":
|
|
281
|
+
continue
|
|
282
|
+
if selection == "none":
|
|
283
|
+
mapping[agent_id] = []
|
|
284
|
+
elif selection == "all":
|
|
285
|
+
mapping[agent_id] = skill_list.copy()
|
|
286
|
+
else:
|
|
287
|
+
# Parse comma-separated numbers
|
|
288
|
+
try:
|
|
289
|
+
selected_indices = [
|
|
290
|
+
int(idx.strip()) for idx in selection.split(",")
|
|
291
|
+
]
|
|
292
|
+
selected_skills = [
|
|
293
|
+
skill_list[idx - 1]
|
|
294
|
+
for idx in selected_indices
|
|
295
|
+
if 1 <= idx <= len(skill_list)
|
|
296
|
+
]
|
|
297
|
+
mapping[agent_id] = selected_skills
|
|
298
|
+
except (ValueError, IndexError) as e:
|
|
299
|
+
print(f" ⚠️ Invalid selection, keeping current: {e}")
|
|
300
|
+
|
|
301
|
+
return mapping
|
|
302
|
+
|
|
303
|
+
def _get_agents_to_configure(self) -> List[str]:
|
|
304
|
+
"""Ask user which agents to configure.
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
List of agent IDs
|
|
308
|
+
"""
|
|
309
|
+
agent_ids_input = input("\nEnter agent IDs (comma-separated): ").strip()
|
|
310
|
+
return [aid.strip() for aid in agent_ids_input.split(",") if aid.strip()]
|
|
311
|
+
|
|
312
|
+
def _preview_final_configuration(self, mapping: Dict[str, List[str]]):
|
|
313
|
+
"""Display final skills configuration preview.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
mapping: Agent-to-skills mapping
|
|
317
|
+
"""
|
|
318
|
+
print("\n" + "=" * 60)
|
|
319
|
+
print("📋 Final Skills Configuration:")
|
|
320
|
+
print("=" * 60)
|
|
321
|
+
|
|
322
|
+
if not mapping:
|
|
323
|
+
print(" (No skills configured)")
|
|
324
|
+
return
|
|
325
|
+
|
|
326
|
+
for agent_id, skills in mapping.items():
|
|
327
|
+
print(f"\n {agent_id} ({len(skills)} skills):")
|
|
328
|
+
if skills:
|
|
329
|
+
for skill in skills:
|
|
330
|
+
print(f" ✓ {skill}")
|
|
331
|
+
else:
|
|
332
|
+
print(" (no skills)")
|
|
333
|
+
|
|
334
|
+
def _apply_skills_configuration(self, mapping: Dict[str, List[str]]):
|
|
335
|
+
"""Apply skills configuration to skill manager.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
mapping: Agent-to-skills mapping
|
|
339
|
+
"""
|
|
340
|
+
for agent_id, skills in mapping.items():
|
|
341
|
+
# Clear existing mappings for this agent
|
|
342
|
+
if agent_id in self.manager.agent_skill_mapping:
|
|
343
|
+
self.manager.agent_skill_mapping[agent_id] = []
|
|
344
|
+
|
|
345
|
+
# Add each skill
|
|
346
|
+
for skill_name in skills:
|
|
347
|
+
self.manager.add_skill_to_agent(agent_id, skill_name)
|
|
348
|
+
|
|
349
|
+
self.logger.info(f"Applied skills configuration for {len(mapping)} agents")
|
|
350
|
+
|
|
351
|
+
def list_available_skills(self):
|
|
352
|
+
"""Display all available skills."""
|
|
353
|
+
print("\n" + "=" * 60)
|
|
354
|
+
print("📚 Available Skills")
|
|
355
|
+
print("=" * 60)
|
|
356
|
+
|
|
357
|
+
# Bundled skills
|
|
358
|
+
bundled_skills = self.registry.list_skills(source="bundled")
|
|
359
|
+
if bundled_skills:
|
|
360
|
+
print(f"\n🔹 Bundled Skills ({len(bundled_skills)}):")
|
|
361
|
+
for skill in sorted(bundled_skills, key=lambda s: s.name):
|
|
362
|
+
print(f" • {skill.name}")
|
|
363
|
+
if skill.description:
|
|
364
|
+
desc = (
|
|
365
|
+
skill.description[:80] + "..."
|
|
366
|
+
if len(skill.description) > 80
|
|
367
|
+
else skill.description
|
|
368
|
+
)
|
|
369
|
+
print(f" {desc}")
|
|
370
|
+
|
|
371
|
+
# User skills
|
|
372
|
+
user_skills = self.registry.list_skills(source="user")
|
|
373
|
+
if user_skills:
|
|
374
|
+
print(f"\n👤 User Skills ({len(user_skills)}):")
|
|
375
|
+
for skill in sorted(user_skills, key=lambda s: s.name):
|
|
376
|
+
print(f" • {skill.name}")
|
|
377
|
+
if skill.description:
|
|
378
|
+
desc = (
|
|
379
|
+
skill.description[:80] + "..."
|
|
380
|
+
if len(skill.description) > 80
|
|
381
|
+
else skill.description
|
|
382
|
+
)
|
|
383
|
+
print(f" {desc}")
|
|
384
|
+
|
|
385
|
+
# Project skills
|
|
386
|
+
project_skills = self.registry.list_skills(source="project")
|
|
387
|
+
if project_skills:
|
|
388
|
+
print(f"\n📂 Project Skills ({len(project_skills)}):")
|
|
389
|
+
for skill in sorted(project_skills, key=lambda s: s.name):
|
|
390
|
+
print(f" • {skill.name}")
|
|
391
|
+
if skill.description:
|
|
392
|
+
desc = (
|
|
393
|
+
skill.description[:80] + "..."
|
|
394
|
+
if len(skill.description) > 80
|
|
395
|
+
else skill.description
|
|
396
|
+
)
|
|
397
|
+
print(f" {desc}")
|
|
398
|
+
|
|
399
|
+
print()
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def discover_and_link_runtime_skills():
|
|
403
|
+
"""Discover user/project skills and auto-link to agents at runtime.
|
|
404
|
+
|
|
405
|
+
This function is called during startup to:
|
|
406
|
+
1. Reload the skills registry (picks up new skills from .claude/skills/)
|
|
407
|
+
2. Auto-link discovered skills to agents based on tags/naming conventions
|
|
408
|
+
"""
|
|
409
|
+
try:
|
|
410
|
+
registry = get_registry()
|
|
411
|
+
manager = get_manager()
|
|
412
|
+
|
|
413
|
+
# Reload registry to pick up new skills
|
|
414
|
+
registry.reload()
|
|
415
|
+
|
|
416
|
+
# Get discovered skills (user and project)
|
|
417
|
+
discovered_skills = registry.list_skills(source="user") + registry.list_skills(
|
|
418
|
+
source="project"
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
if not discovered_skills:
|
|
422
|
+
logger.debug("No runtime skills discovered")
|
|
423
|
+
return
|
|
424
|
+
|
|
425
|
+
logger.info(f"Discovered {len(discovered_skills)} runtime skills")
|
|
426
|
+
|
|
427
|
+
# Auto-link based on skill content and naming
|
|
428
|
+
for skill in discovered_skills:
|
|
429
|
+
agents = _infer_agents_for_skill(skill)
|
|
430
|
+
for agent_id in agents:
|
|
431
|
+
manager.add_skill_to_agent(agent_id, skill.name)
|
|
432
|
+
logger.debug(f"Auto-linked skill '{skill.name}' to agent '{agent_id}'")
|
|
433
|
+
|
|
434
|
+
except Exception as e:
|
|
435
|
+
logger.error(f"Error during runtime skills discovery: {e}", exc_info=True)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _infer_agents_for_skill(skill) -> List[str]:
|
|
439
|
+
"""Infer which agents should have this skill based on tags/name.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
skill: Skill object to analyze
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
List of agent IDs that should have this skill
|
|
446
|
+
"""
|
|
447
|
+
agents = []
|
|
448
|
+
content_lower = skill.content.lower()
|
|
449
|
+
name_lower = skill.name.lower()
|
|
450
|
+
|
|
451
|
+
# Python-related
|
|
452
|
+
if any(
|
|
453
|
+
tag in content_lower or tag in name_lower
|
|
454
|
+
for tag in ["python", "django", "flask", "fastapi"]
|
|
455
|
+
):
|
|
456
|
+
agents.append("python-engineer")
|
|
457
|
+
|
|
458
|
+
# TypeScript/JavaScript-related
|
|
459
|
+
if any(
|
|
460
|
+
tag in content_lower or tag in name_lower
|
|
461
|
+
for tag in ["typescript", "javascript", "react", "next", "vue", "node"]
|
|
462
|
+
):
|
|
463
|
+
agents.extend(["typescript-engineer", "react-engineer", "nextjs-engineer"])
|
|
464
|
+
|
|
465
|
+
# Go-related
|
|
466
|
+
if any(tag in content_lower or tag in name_lower for tag in ["golang", "go "]):
|
|
467
|
+
agents.append("golang-engineer")
|
|
468
|
+
|
|
469
|
+
# Ops-related
|
|
470
|
+
if any(
|
|
471
|
+
tag in content_lower or tag in name_lower
|
|
472
|
+
for tag in ["docker", "kubernetes", "deploy", "devops", "ops"]
|
|
473
|
+
):
|
|
474
|
+
agents.extend(["ops", "devops", "local-ops"])
|
|
475
|
+
|
|
476
|
+
# Testing/QA-related
|
|
477
|
+
if any(
|
|
478
|
+
tag in content_lower or tag in name_lower
|
|
479
|
+
for tag in ["test", "qa", "quality", "assert"]
|
|
480
|
+
):
|
|
481
|
+
agents.extend(["qa", "web-qa", "api-qa"])
|
|
482
|
+
|
|
483
|
+
# Documentation-related
|
|
484
|
+
if any(
|
|
485
|
+
tag in content_lower or tag in name_lower
|
|
486
|
+
for tag in ["documentation", "docs", "api doc", "openapi"]
|
|
487
|
+
):
|
|
488
|
+
agents.extend(["docs", "documentation", "technical-writer"])
|
|
489
|
+
|
|
490
|
+
# Remove duplicates
|
|
491
|
+
return list(set(agents))
|
claude_mpm/cli/startup.py
CHANGED
|
@@ -79,6 +79,31 @@ def setup_configure_command_environment(args):
|
|
|
79
79
|
logging.getLogger("claude_mpm").setLevel(logging.WARNING)
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
def discover_and_link_runtime_skills():
|
|
83
|
+
"""
|
|
84
|
+
Discover and link runtime skills from user/project directories.
|
|
85
|
+
|
|
86
|
+
WHY: Automatically discover and link skills added to .claude/skills/
|
|
87
|
+
without requiring manual configuration.
|
|
88
|
+
|
|
89
|
+
DESIGN DECISION: Failures are logged but don't block startup to ensure
|
|
90
|
+
claude-mpm remains functional even if skills discovery fails.
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
from ..cli.interactive.skills_wizard import (
|
|
94
|
+
discover_and_link_runtime_skills as discover_skills,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
discover_skills()
|
|
98
|
+
except Exception as e:
|
|
99
|
+
# Import logger here to avoid circular imports
|
|
100
|
+
from ..core.logger import get_logger
|
|
101
|
+
|
|
102
|
+
logger = get_logger("cli")
|
|
103
|
+
logger.debug(f"Failed to discover runtime skills: {e}")
|
|
104
|
+
# Continue execution - skills discovery failure shouldn't block startup
|
|
105
|
+
|
|
106
|
+
|
|
82
107
|
def run_background_services():
|
|
83
108
|
"""
|
|
84
109
|
Initialize all background services on startup.
|
|
@@ -89,6 +114,7 @@ def run_background_services():
|
|
|
89
114
|
check_mcp_auto_configuration()
|
|
90
115
|
verify_mcp_gateway_startup()
|
|
91
116
|
check_for_updates_async()
|
|
117
|
+
discover_and_link_runtime_skills()
|
|
92
118
|
|
|
93
119
|
|
|
94
120
|
def setup_mcp_server_logging(args):
|
|
@@ -321,14 +321,6 @@ class Dashboard {
|
|
|
321
321
|
// Process agent inference for new events
|
|
322
322
|
this.agentInference.processAgentInference();
|
|
323
323
|
|
|
324
|
-
// Notify CodeViewer that file operations have been updated
|
|
325
|
-
// This ensures File Tree tab shows the same data as Files tab
|
|
326
|
-
if (window.CodeViewer && typeof window.CodeViewer.refreshFromFileToolTracker === 'function') {
|
|
327
|
-
setTimeout(() => {
|
|
328
|
-
window.CodeViewer.refreshFromFileToolTracker();
|
|
329
|
-
}, 50);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
324
|
// Update agent hierarchy with new events
|
|
333
325
|
this.agentHierarchy.updateWithNewEvents(events);
|
|
334
326
|
|
|
@@ -445,12 +437,6 @@ class Dashboard {
|
|
|
445
437
|
case 'events':
|
|
446
438
|
// Events tab is handled by EventViewer
|
|
447
439
|
break;
|
|
448
|
-
case 'claude-tree':
|
|
449
|
-
// File Tree tab - trigger CodeViewer rendering
|
|
450
|
-
if (window.CodeViewer && typeof window.CodeViewer.show === 'function') {
|
|
451
|
-
window.CodeViewer.show();
|
|
452
|
-
}
|
|
453
|
-
break;
|
|
454
440
|
case 'activity':
|
|
455
441
|
// Trigger Activity tab rendering through the component
|
|
456
442
|
// Check if ActivityTree class is available (from built module)
|
|
@@ -33,7 +33,6 @@
|
|
|
33
33
|
<link rel="stylesheet" href="/static/css/dashboard.css">
|
|
34
34
|
<link rel="stylesheet" href="/static/css/connection-status.css">
|
|
35
35
|
<link rel="stylesheet" href="/static/css/activity.css">
|
|
36
|
-
<link rel="stylesheet" href="/static/css/code-tree.css">
|
|
37
36
|
|
|
38
37
|
<!-- Additional styles for file operations -->
|
|
39
38
|
<style>
|
|
@@ -258,7 +257,6 @@
|
|
|
258
257
|
<a href="#tools" class="tab-button" data-tab="tools">🔧 Tools</a>
|
|
259
258
|
<a href="#files" class="tab-button" data-tab="files">📁 Files</a>
|
|
260
259
|
<a href="#activity" class="tab-button" data-tab="activity">🌳 Activity</a>
|
|
261
|
-
<a href="#file_tree" class="tab-button" data-tab="claude-tree">📝 File Tree</a>
|
|
262
260
|
</div>
|
|
263
261
|
|
|
264
262
|
<!-- Events Tab -->
|
|
@@ -397,15 +395,6 @@
|
|
|
397
395
|
</div>
|
|
398
396
|
</div>
|
|
399
397
|
|
|
400
|
-
<!-- File Tree Tab -->
|
|
401
|
-
<div class="tab-content" id="claude-tree-tab">
|
|
402
|
-
<div id="claude-tree-container" style="width: 100%; height: 100%; position: relative;">
|
|
403
|
-
<!-- File activity tree will be rendered here by code-viewer.js -->
|
|
404
|
-
<!-- This container is ISOLATED from other tabs -->
|
|
405
|
-
</div>
|
|
406
|
-
</div>
|
|
407
|
-
|
|
408
|
-
|
|
409
398
|
</div>
|
|
410
399
|
</div>
|
|
411
400
|
</div>
|
|
@@ -489,16 +478,12 @@
|
|
|
489
478
|
});
|
|
490
479
|
};
|
|
491
480
|
|
|
492
|
-
// Load shared services first, then
|
|
481
|
+
// Load shared services first, then components
|
|
493
482
|
// Load services sequentially to ensure dependencies are available
|
|
494
483
|
loadModule('/static/js/shared/tooltip-service.js')
|
|
495
484
|
.then(() => loadModule('/static/js/shared/dom-helpers.js'))
|
|
496
485
|
.then(() => loadModule('/static/js/shared/event-bus.js'))
|
|
497
486
|
.then(() => loadModule('/static/js/shared/logger.js'))
|
|
498
|
-
.then(() => loadModule('/static/js/components/code-tree/tree-utils.js'))
|
|
499
|
-
.then(() => loadModule('/static/js/components/code-tree/tree-constants.js'))
|
|
500
|
-
.then(() => loadModule('/static/js/components/code-tree/tree-search.js'))
|
|
501
|
-
.then(() => loadModule('/static/js/components/code-tree/tree-breadcrumb.js'))
|
|
502
487
|
.then(() => {
|
|
503
488
|
// CRITICAL: Load socket-client.js FIRST (dependency of socket-manager)
|
|
504
489
|
return loadModule('/static/js/socket-client.js');
|
|
@@ -530,29 +515,15 @@
|
|
|
530
515
|
return Promise.all([
|
|
531
516
|
loadModule('/static/dist/dashboard.js'), // Use dist version that requires above components
|
|
532
517
|
loadModule('/static/dist/components/activity-tree.js'),
|
|
533
|
-
loadModule('/static/js/components/code-tree.js'), // TEMPORARY: Direct source for debugging
|
|
534
|
-
loadModule('/static/js/components/code-viewer.js').catch(err => {
|
|
535
|
-
console.error('[CRITICAL] Failed to load code-viewer.js:', err);
|
|
536
|
-
throw err;
|
|
537
|
-
}), // Code viewer now includes file change tracking
|
|
538
518
|
loadModule('/static/dist/components/file-viewer.js') // File viewer for viewing file contents
|
|
539
519
|
]);
|
|
540
520
|
})
|
|
541
521
|
.then(() => {
|
|
542
522
|
console.log('All dashboard modules loaded successfully');
|
|
543
|
-
|
|
544
|
-
// Debug: Check if CodeViewer loaded
|
|
545
|
-
if (window.CodeViewer) {
|
|
546
|
-
console.log('[DEBUG] CodeViewer is available on window object');
|
|
547
|
-
} else {
|
|
548
|
-
console.error('[ERROR] CodeViewer NOT FOUND on window object!');
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// CodeViewer will auto-initialize and handle tab switching internally
|
|
552
|
-
|
|
523
|
+
|
|
553
524
|
// Browser Log Viewer initialization is now handled by UIStateManager
|
|
554
525
|
// This prevents duplicate event handlers and tab selection conflicts
|
|
555
|
-
|
|
526
|
+
|
|
556
527
|
// Load bulletproof tab isolation fix
|
|
557
528
|
loadModule('/static/js/tab-isolation-fix.js')
|
|
558
529
|
.then(() => {
|
|
@@ -564,15 +535,6 @@
|
|
|
564
535
|
|
|
565
536
|
// Hash navigation will handle default tab based on URL
|
|
566
537
|
// If no hash, default will be 'events' as per hashToTab mapping
|
|
567
|
-
// To start with File Tree, we can set hash if not present
|
|
568
|
-
setTimeout(() => {
|
|
569
|
-
if (!window.location.hash) {
|
|
570
|
-
console.log('No hash present, setting default to File Tree tab...');
|
|
571
|
-
window.location.hash = '#file_tree';
|
|
572
|
-
} else {
|
|
573
|
-
console.log('Hash present:', window.location.hash);
|
|
574
|
-
}
|
|
575
|
-
}, 500);
|
|
576
538
|
})
|
|
577
539
|
.catch(error => {
|
|
578
540
|
console.error('[CRITICAL] Error loading dashboard modules:', error);
|
|
@@ -7,7 +7,9 @@ and maintainability.
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from .base import BaseEventHandler
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
# DISABLED: File Tree interface removed from dashboard
|
|
12
|
+
# from .code_analysis import CodeAnalysisEventHandler
|
|
11
13
|
from .connection import ConnectionEventHandler
|
|
12
14
|
from .file import FileEventHandler
|
|
13
15
|
from .git import GitEventHandler
|
|
@@ -17,7 +19,8 @@ from .registry import EventHandlerRegistry
|
|
|
17
19
|
|
|
18
20
|
__all__ = [
|
|
19
21
|
"BaseEventHandler",
|
|
20
|
-
|
|
22
|
+
# DISABLED: File Tree interface removed from dashboard
|
|
23
|
+
# "CodeAnalysisEventHandler",
|
|
21
24
|
"ConnectionEventHandler",
|
|
22
25
|
"EventHandlerRegistry",
|
|
23
26
|
"FileEventHandler",
|