claude-mpm 4.15.2__py3-none-any.whl → 4.15.6__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.

Files changed (85) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/templates/agentic-coder-optimizer.json +9 -2
  3. claude_mpm/agents/templates/api_qa.json +7 -1
  4. claude_mpm/agents/templates/clerk-ops.json +8 -1
  5. claude_mpm/agents/templates/code_analyzer.json +4 -1
  6. claude_mpm/agents/templates/dart_engineer.json +11 -1
  7. claude_mpm/agents/templates/data_engineer.json +11 -1
  8. claude_mpm/agents/templates/documentation.json +6 -1
  9. claude_mpm/agents/templates/engineer.json +13 -0
  10. claude_mpm/agents/templates/gcp_ops_agent.json +8 -1
  11. claude_mpm/agents/templates/golang_engineer.json +11 -1
  12. claude_mpm/agents/templates/java_engineer.json +12 -2
  13. claude_mpm/agents/templates/local_ops_agent.json +216 -37
  14. claude_mpm/agents/templates/nextjs_engineer.json +11 -1
  15. claude_mpm/agents/templates/ops.json +8 -1
  16. claude_mpm/agents/templates/php-engineer.json +11 -1
  17. claude_mpm/agents/templates/project_organizer.json +9 -2
  18. claude_mpm/agents/templates/prompt-engineer.json +5 -1
  19. claude_mpm/agents/templates/python_engineer.json +11 -1
  20. claude_mpm/agents/templates/qa.json +7 -1
  21. claude_mpm/agents/templates/react_engineer.json +11 -1
  22. claude_mpm/agents/templates/refactoring_engineer.json +8 -1
  23. claude_mpm/agents/templates/research.json +4 -1
  24. claude_mpm/agents/templates/ruby-engineer.json +11 -1
  25. claude_mpm/agents/templates/rust_engineer.json +11 -1
  26. claude_mpm/agents/templates/security.json +6 -1
  27. claude_mpm/agents/templates/ticketing.json +6 -1
  28. claude_mpm/agents/templates/typescript_engineer.json +11 -1
  29. claude_mpm/agents/templates/vercel_ops_agent.json +8 -1
  30. claude_mpm/agents/templates/version_control.json +8 -1
  31. claude_mpm/agents/templates/web_qa.json +7 -1
  32. claude_mpm/agents/templates/web_ui.json +11 -1
  33. claude_mpm/cli/commands/configure.py +164 -16
  34. claude_mpm/cli/commands/configure_agent_display.py +6 -6
  35. claude_mpm/cli/commands/configure_behavior_manager.py +8 -8
  36. claude_mpm/cli/commands/configure_navigation.py +20 -18
  37. claude_mpm/cli/commands/configure_startup_manager.py +14 -14
  38. claude_mpm/cli/commands/configure_template_editor.py +8 -8
  39. claude_mpm/cli/interactive/__init__.py +3 -0
  40. claude_mpm/cli/interactive/skills_wizard.py +491 -0
  41. claude_mpm/cli/startup.py +26 -0
  42. claude_mpm/core/enums.py +18 -0
  43. claude_mpm/core/types.py +2 -9
  44. claude_mpm/dashboard/static/js/dashboard.js +0 -14
  45. claude_mpm/dashboard/templates/index.html +3 -41
  46. claude_mpm/services/agents/deployment/validation/__init__.py +3 -1
  47. claude_mpm/services/agents/deployment/validation/validation_result.py +1 -9
  48. claude_mpm/services/core/models/health.py +1 -28
  49. claude_mpm/services/infrastructure/monitoring/__init__.py +1 -1
  50. claude_mpm/services/infrastructure/monitoring/aggregator.py +12 -12
  51. claude_mpm/services/infrastructure/monitoring/base.py +5 -13
  52. claude_mpm/services/infrastructure/monitoring/network.py +7 -6
  53. claude_mpm/services/infrastructure/monitoring/process.py +13 -12
  54. claude_mpm/services/infrastructure/monitoring/resources.py +7 -6
  55. claude_mpm/services/infrastructure/monitoring/service.py +16 -15
  56. claude_mpm/services/local_ops/__init__.py +1 -1
  57. claude_mpm/services/local_ops/crash_detector.py +1 -1
  58. claude_mpm/services/local_ops/health_checks/http_check.py +2 -1
  59. claude_mpm/services/local_ops/health_checks/process_check.py +2 -1
  60. claude_mpm/services/local_ops/health_checks/resource_check.py +2 -1
  61. claude_mpm/services/local_ops/health_manager.py +1 -1
  62. claude_mpm/services/local_ops/restart_manager.py +1 -1
  63. claude_mpm/services/shared/async_service_base.py +16 -27
  64. claude_mpm/services/shared/lifecycle_service_base.py +1 -14
  65. claude_mpm/services/socketio/handlers/__init__.py +5 -2
  66. claude_mpm/services/socketio/handlers/hook.py +10 -0
  67. claude_mpm/services/socketio/handlers/registry.py +4 -2
  68. claude_mpm/services/socketio/server/main.py +7 -7
  69. claude_mpm/skills/__init__.py +21 -0
  70. claude_mpm/skills/bundled/__init__.py +6 -0
  71. claude_mpm/skills/registry.py +198 -0
  72. claude_mpm/skills/skill_manager.py +310 -0
  73. {claude_mpm-4.15.2.dist-info → claude_mpm-4.15.6.dist-info}/METADATA +1 -1
  74. {claude_mpm-4.15.2.dist-info → claude_mpm-4.15.6.dist-info}/RECORD +78 -80
  75. claude_mpm/dashboard/static/css/code-tree.css +0 -1639
  76. claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +0 -353
  77. claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +0 -235
  78. claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +0 -409
  79. claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +0 -435
  80. claude_mpm/dashboard/static/js/components/code-tree.js +0 -5869
  81. claude_mpm/dashboard/static/js/components/code-viewer.js +0 -1386
  82. {claude_mpm-4.15.2.dist-info → claude_mpm-4.15.6.dist-info}/WHEEL +0 -0
  83. {claude_mpm-4.15.2.dist-info → claude_mpm-4.15.6.dist-info}/entry_points.txt +0 -0
  84. {claude_mpm-4.15.2.dist-info → claude_mpm-4.15.6.dist-info}/licenses/LICENSE +0 -0
  85. {claude_mpm-4.15.2.dist-info → claude_mpm-4.15.6.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):
claude_mpm/core/enums.py CHANGED
@@ -251,6 +251,24 @@ class HealthStatus(StrEnum):
251
251
  TIMEOUT = "timeout"
252
252
  """Health check exceeded time limit."""
253
253
 
254
+ def is_operational(self) -> bool:
255
+ """
256
+ Check if health status indicates operational state.
257
+
258
+ Returns:
259
+ True if status is HEALTHY or DEGRADED
260
+ """
261
+ return self in (HealthStatus.HEALTHY, HealthStatus.DEGRADED)
262
+
263
+ def is_critical(self) -> bool:
264
+ """
265
+ Check if health status indicates critical failure.
266
+
267
+ Returns:
268
+ True if status is UNHEALTHY
269
+ """
270
+ return self == HealthStatus.UNHEALTHY
271
+
254
272
 
255
273
  class ModelTier(StrEnum):
256
274
  """
claude_mpm/core/types.py CHANGED
@@ -20,6 +20,8 @@ from datetime import datetime, timezone
20
20
  from enum import Enum
21
21
  from typing import Any, Dict, List, Optional, Tuple, Union
22
22
 
23
+ from .enums import HealthStatus
24
+
23
25
 
24
26
  # Service operation results
25
27
  @dataclass
@@ -230,15 +232,6 @@ class SocketMessage:
230
232
 
231
233
 
232
234
  # Health monitoring types
233
- class HealthStatus(Enum):
234
- """Service health status levels."""
235
-
236
- HEALTHY = "healthy"
237
- DEGRADED = "degraded"
238
- UNHEALTHY = "unhealthy"
239
- UNKNOWN = "unknown"
240
-
241
-
242
235
  @dataclass
243
236
  class HealthCheck:
244
237
  """Health check result."""
@@ -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)