claude-mpm 4.7.4__py3-none-any.whl → 4.18.2__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 (308) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +118 -0
  3. claude_mpm/agents/BASE_ENGINEER.md +286 -0
  4. claude_mpm/agents/BASE_PM.md +106 -1
  5. claude_mpm/agents/OUTPUT_STYLE.md +329 -11
  6. claude_mpm/agents/PM_INSTRUCTIONS.md +397 -459
  7. claude_mpm/agents/agent_loader.py +17 -5
  8. claude_mpm/agents/frontmatter_validator.py +284 -253
  9. claude_mpm/agents/templates/README.md +465 -0
  10. claude_mpm/agents/templates/agent-manager.json +4 -1
  11. claude_mpm/agents/templates/agentic-coder-optimizer.json +13 -3
  12. claude_mpm/agents/templates/api_qa.json +11 -2
  13. claude_mpm/agents/templates/circuit_breakers.md +638 -0
  14. claude_mpm/agents/templates/clerk-ops.json +12 -2
  15. claude_mpm/agents/templates/code_analyzer.json +8 -2
  16. claude_mpm/agents/templates/content-agent.json +358 -0
  17. claude_mpm/agents/templates/dart_engineer.json +15 -2
  18. claude_mpm/agents/templates/data_engineer.json +15 -2
  19. claude_mpm/agents/templates/documentation.json +10 -2
  20. claude_mpm/agents/templates/engineer.json +21 -1
  21. claude_mpm/agents/templates/gcp_ops_agent.json +12 -2
  22. claude_mpm/agents/templates/git_file_tracking.md +584 -0
  23. claude_mpm/agents/templates/golang_engineer.json +270 -0
  24. claude_mpm/agents/templates/imagemagick.json +4 -1
  25. claude_mpm/agents/templates/java_engineer.json +346 -0
  26. claude_mpm/agents/templates/local_ops_agent.json +1227 -6
  27. claude_mpm/agents/templates/memory_manager.json +4 -1
  28. claude_mpm/agents/templates/nextjs_engineer.json +141 -133
  29. claude_mpm/agents/templates/ops.json +12 -2
  30. claude_mpm/agents/templates/php-engineer.json +270 -174
  31. claude_mpm/agents/templates/pm_examples.md +474 -0
  32. claude_mpm/agents/templates/pm_red_flags.md +240 -0
  33. claude_mpm/agents/templates/product_owner.json +338 -0
  34. claude_mpm/agents/templates/project_organizer.json +14 -4
  35. claude_mpm/agents/templates/prompt-engineer.json +13 -2
  36. claude_mpm/agents/templates/python_engineer.json +174 -81
  37. claude_mpm/agents/templates/qa.json +11 -2
  38. claude_mpm/agents/templates/react_engineer.json +16 -3
  39. claude_mpm/agents/templates/refactoring_engineer.json +12 -2
  40. claude_mpm/agents/templates/research.json +34 -21
  41. claude_mpm/agents/templates/response_format.md +583 -0
  42. claude_mpm/agents/templates/ruby-engineer.json +129 -192
  43. claude_mpm/agents/templates/rust_engineer.json +270 -0
  44. claude_mpm/agents/templates/security.json +10 -2
  45. claude_mpm/agents/templates/svelte-engineer.json +225 -0
  46. claude_mpm/agents/templates/ticketing.json +10 -2
  47. claude_mpm/agents/templates/typescript_engineer.json +116 -125
  48. claude_mpm/agents/templates/validation_templates.md +312 -0
  49. claude_mpm/agents/templates/vercel_ops_agent.json +12 -2
  50. claude_mpm/agents/templates/version_control.json +12 -2
  51. claude_mpm/agents/templates/web_qa.json +11 -2
  52. claude_mpm/agents/templates/web_ui.json +15 -2
  53. claude_mpm/cli/__init__.py +34 -614
  54. claude_mpm/cli/commands/agent_manager.py +25 -12
  55. claude_mpm/cli/commands/agent_state_manager.py +186 -0
  56. claude_mpm/cli/commands/agents.py +235 -148
  57. claude_mpm/cli/commands/agents_detect.py +380 -0
  58. claude_mpm/cli/commands/agents_recommend.py +309 -0
  59. claude_mpm/cli/commands/aggregate.py +7 -3
  60. claude_mpm/cli/commands/analyze.py +9 -4
  61. claude_mpm/cli/commands/analyze_code.py +7 -2
  62. claude_mpm/cli/commands/auto_configure.py +570 -0
  63. claude_mpm/cli/commands/config.py +47 -13
  64. claude_mpm/cli/commands/configure.py +419 -1571
  65. claude_mpm/cli/commands/configure_agent_display.py +261 -0
  66. claude_mpm/cli/commands/configure_behavior_manager.py +204 -0
  67. claude_mpm/cli/commands/configure_hook_manager.py +225 -0
  68. claude_mpm/cli/commands/configure_models.py +18 -0
  69. claude_mpm/cli/commands/configure_navigation.py +167 -0
  70. claude_mpm/cli/commands/configure_paths.py +104 -0
  71. claude_mpm/cli/commands/configure_persistence.py +254 -0
  72. claude_mpm/cli/commands/configure_startup_manager.py +646 -0
  73. claude_mpm/cli/commands/configure_template_editor.py +497 -0
  74. claude_mpm/cli/commands/configure_validators.py +73 -0
  75. claude_mpm/cli/commands/local_deploy.py +537 -0
  76. claude_mpm/cli/commands/memory.py +54 -20
  77. claude_mpm/cli/commands/mpm_init.py +585 -196
  78. claude_mpm/cli/commands/mpm_init_handler.py +37 -3
  79. claude_mpm/cli/commands/search.py +170 -4
  80. claude_mpm/cli/commands/upgrade.py +152 -0
  81. claude_mpm/cli/executor.py +202 -0
  82. claude_mpm/cli/helpers.py +105 -0
  83. claude_mpm/cli/interactive/__init__.py +3 -0
  84. claude_mpm/cli/interactive/skills_wizard.py +491 -0
  85. claude_mpm/cli/parsers/__init__.py +7 -1
  86. claude_mpm/cli/parsers/agents_parser.py +9 -0
  87. claude_mpm/cli/parsers/auto_configure_parser.py +245 -0
  88. claude_mpm/cli/parsers/base_parser.py +110 -3
  89. claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
  90. claude_mpm/cli/parsers/mpm_init_parser.py +65 -5
  91. claude_mpm/cli/shared/output_formatters.py +28 -19
  92. claude_mpm/cli/startup.py +481 -0
  93. claude_mpm/cli/utils.py +52 -1
  94. claude_mpm/commands/mpm-agents-detect.md +168 -0
  95. claude_mpm/commands/mpm-agents-recommend.md +214 -0
  96. claude_mpm/commands/mpm-agents.md +75 -1
  97. claude_mpm/commands/mpm-auto-configure.md +217 -0
  98. claude_mpm/commands/mpm-help.md +163 -0
  99. claude_mpm/commands/mpm-init.md +148 -3
  100. claude_mpm/commands/mpm-version.md +113 -0
  101. claude_mpm/commands/mpm.md +1 -0
  102. claude_mpm/config/agent_config.py +2 -2
  103. claude_mpm/config/model_config.py +428 -0
  104. claude_mpm/constants.py +1 -0
  105. claude_mpm/core/base_service.py +13 -12
  106. claude_mpm/core/enums.py +452 -0
  107. claude_mpm/core/factories.py +1 -1
  108. claude_mpm/core/instruction_reinforcement_hook.py +2 -1
  109. claude_mpm/core/interactive_session.py +9 -3
  110. claude_mpm/core/log_manager.py +2 -0
  111. claude_mpm/core/logging_config.py +6 -2
  112. claude_mpm/core/oneshot_session.py +8 -4
  113. claude_mpm/core/optimized_agent_loader.py +3 -3
  114. claude_mpm/core/output_style_manager.py +12 -192
  115. claude_mpm/core/service_registry.py +5 -1
  116. claude_mpm/core/types.py +2 -9
  117. claude_mpm/core/typing_utils.py +7 -6
  118. claude_mpm/dashboard/static/js/dashboard.js +0 -14
  119. claude_mpm/dashboard/templates/index.html +3 -41
  120. claude_mpm/hooks/__init__.py +20 -0
  121. claude_mpm/hooks/claude_hooks/event_handlers.py +4 -2
  122. claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
  123. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +23 -2
  124. claude_mpm/hooks/failure_learning/__init__.py +60 -0
  125. claude_mpm/hooks/failure_learning/failure_detection_hook.py +235 -0
  126. claude_mpm/hooks/failure_learning/fix_detection_hook.py +217 -0
  127. claude_mpm/hooks/failure_learning/learning_extraction_hook.py +286 -0
  128. claude_mpm/hooks/instruction_reinforcement.py +7 -2
  129. claude_mpm/hooks/kuzu_enrichment_hook.py +263 -0
  130. claude_mpm/hooks/kuzu_memory_hook.py +37 -12
  131. claude_mpm/hooks/kuzu_response_hook.py +183 -0
  132. claude_mpm/models/resume_log.py +340 -0
  133. claude_mpm/services/agents/__init__.py +18 -5
  134. claude_mpm/services/agents/auto_config_manager.py +796 -0
  135. claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
  136. claude_mpm/services/agents/deployment/agent_record_service.py +1 -1
  137. claude_mpm/services/agents/deployment/agent_validator.py +17 -1
  138. claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
  139. claude_mpm/services/agents/deployment/interface_adapter.py +3 -2
  140. claude_mpm/services/agents/deployment/local_template_deployment.py +1 -1
  141. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +7 -6
  142. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +7 -16
  143. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +4 -3
  144. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +5 -3
  145. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +6 -5
  146. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +9 -6
  147. claude_mpm/services/agents/deployment/validation/__init__.py +3 -1
  148. claude_mpm/services/agents/deployment/validation/validation_result.py +1 -9
  149. claude_mpm/services/agents/local_template_manager.py +1 -1
  150. claude_mpm/services/agents/memory/agent_memory_manager.py +5 -2
  151. claude_mpm/services/agents/observers.py +547 -0
  152. claude_mpm/services/agents/recommender.py +568 -0
  153. claude_mpm/services/agents/registry/modification_tracker.py +5 -2
  154. claude_mpm/services/command_handler_service.py +11 -5
  155. claude_mpm/services/core/__init__.py +33 -1
  156. claude_mpm/services/core/interfaces/__init__.py +90 -3
  157. claude_mpm/services/core/interfaces/agent.py +184 -0
  158. claude_mpm/services/core/interfaces/health.py +172 -0
  159. claude_mpm/services/core/interfaces/model.py +281 -0
  160. claude_mpm/services/core/interfaces/process.py +372 -0
  161. claude_mpm/services/core/interfaces/project.py +121 -0
  162. claude_mpm/services/core/interfaces/restart.py +307 -0
  163. claude_mpm/services/core/interfaces/stability.py +260 -0
  164. claude_mpm/services/core/memory_manager.py +11 -24
  165. claude_mpm/services/core/models/__init__.py +79 -0
  166. claude_mpm/services/core/models/agent_config.py +381 -0
  167. claude_mpm/services/core/models/health.py +162 -0
  168. claude_mpm/services/core/models/process.py +235 -0
  169. claude_mpm/services/core/models/restart.py +302 -0
  170. claude_mpm/services/core/models/stability.py +264 -0
  171. claude_mpm/services/core/models/toolchain.py +306 -0
  172. claude_mpm/services/core/path_resolver.py +23 -7
  173. claude_mpm/services/diagnostics/__init__.py +2 -2
  174. claude_mpm/services/diagnostics/checks/agent_check.py +25 -24
  175. claude_mpm/services/diagnostics/checks/claude_code_check.py +24 -23
  176. claude_mpm/services/diagnostics/checks/common_issues_check.py +25 -24
  177. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -23
  178. claude_mpm/services/diagnostics/checks/filesystem_check.py +18 -17
  179. claude_mpm/services/diagnostics/checks/installation_check.py +30 -29
  180. claude_mpm/services/diagnostics/checks/instructions_check.py +20 -19
  181. claude_mpm/services/diagnostics/checks/mcp_check.py +50 -36
  182. claude_mpm/services/diagnostics/checks/mcp_services_check.py +38 -33
  183. claude_mpm/services/diagnostics/checks/monitor_check.py +23 -22
  184. claude_mpm/services/diagnostics/checks/startup_log_check.py +9 -8
  185. claude_mpm/services/diagnostics/diagnostic_runner.py +6 -5
  186. claude_mpm/services/diagnostics/doctor_reporter.py +28 -25
  187. claude_mpm/services/diagnostics/models.py +19 -24
  188. claude_mpm/services/infrastructure/monitoring/__init__.py +1 -1
  189. claude_mpm/services/infrastructure/monitoring/aggregator.py +12 -12
  190. claude_mpm/services/infrastructure/monitoring/base.py +5 -13
  191. claude_mpm/services/infrastructure/monitoring/network.py +7 -6
  192. claude_mpm/services/infrastructure/monitoring/process.py +13 -12
  193. claude_mpm/services/infrastructure/monitoring/resources.py +7 -6
  194. claude_mpm/services/infrastructure/monitoring/service.py +16 -15
  195. claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
  196. claude_mpm/services/local_ops/__init__.py +163 -0
  197. claude_mpm/services/local_ops/crash_detector.py +257 -0
  198. claude_mpm/services/local_ops/health_checks/__init__.py +28 -0
  199. claude_mpm/services/local_ops/health_checks/http_check.py +224 -0
  200. claude_mpm/services/local_ops/health_checks/process_check.py +236 -0
  201. claude_mpm/services/local_ops/health_checks/resource_check.py +255 -0
  202. claude_mpm/services/local_ops/health_manager.py +430 -0
  203. claude_mpm/services/local_ops/log_monitor.py +396 -0
  204. claude_mpm/services/local_ops/memory_leak_detector.py +294 -0
  205. claude_mpm/services/local_ops/process_manager.py +595 -0
  206. claude_mpm/services/local_ops/resource_monitor.py +331 -0
  207. claude_mpm/services/local_ops/restart_manager.py +401 -0
  208. claude_mpm/services/local_ops/restart_policy.py +387 -0
  209. claude_mpm/services/local_ops/state_manager.py +372 -0
  210. claude_mpm/services/local_ops/unified_manager.py +600 -0
  211. claude_mpm/services/mcp_config_manager.py +9 -4
  212. claude_mpm/services/mcp_gateway/core/__init__.py +1 -2
  213. claude_mpm/services/mcp_gateway/core/base.py +18 -31
  214. claude_mpm/services/mcp_gateway/main.py +30 -0
  215. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +206 -32
  216. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +30 -28
  217. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +25 -5
  218. claude_mpm/services/mcp_service_verifier.py +1 -1
  219. claude_mpm/services/memory/failure_tracker.py +563 -0
  220. claude_mpm/services/memory_hook_service.py +165 -4
  221. claude_mpm/services/model/__init__.py +147 -0
  222. claude_mpm/services/model/base_provider.py +365 -0
  223. claude_mpm/services/model/claude_provider.py +412 -0
  224. claude_mpm/services/model/model_router.py +453 -0
  225. claude_mpm/services/model/ollama_provider.py +415 -0
  226. claude_mpm/services/monitor/daemon_manager.py +3 -2
  227. claude_mpm/services/monitor/handlers/dashboard.py +2 -1
  228. claude_mpm/services/monitor/handlers/hooks.py +2 -1
  229. claude_mpm/services/monitor/management/lifecycle.py +3 -2
  230. claude_mpm/services/monitor/server.py +2 -1
  231. claude_mpm/services/project/__init__.py +23 -0
  232. claude_mpm/services/project/detection_strategies.py +719 -0
  233. claude_mpm/services/project/toolchain_analyzer.py +581 -0
  234. claude_mpm/services/self_upgrade_service.py +342 -0
  235. claude_mpm/services/session_management_service.py +3 -2
  236. claude_mpm/services/session_manager.py +205 -1
  237. claude_mpm/services/shared/async_service_base.py +16 -27
  238. claude_mpm/services/shared/lifecycle_service_base.py +1 -14
  239. claude_mpm/services/socketio/handlers/__init__.py +5 -2
  240. claude_mpm/services/socketio/handlers/hook.py +13 -2
  241. claude_mpm/services/socketio/handlers/registry.py +4 -2
  242. claude_mpm/services/socketio/server/main.py +10 -8
  243. claude_mpm/services/subprocess_launcher_service.py +14 -5
  244. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +8 -7
  245. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +6 -5
  246. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +8 -7
  247. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +7 -6
  248. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +5 -4
  249. claude_mpm/services/unified/config_strategies/validation_strategy.py +13 -9
  250. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +10 -3
  251. claude_mpm/services/unified/deployment_strategies/local.py +6 -5
  252. claude_mpm/services/unified/deployment_strategies/utils.py +6 -5
  253. claude_mpm/services/unified/deployment_strategies/vercel.py +7 -6
  254. claude_mpm/services/unified/interfaces.py +3 -1
  255. claude_mpm/services/unified/unified_analyzer.py +14 -10
  256. claude_mpm/services/unified/unified_config.py +2 -1
  257. claude_mpm/services/unified/unified_deployment.py +9 -4
  258. claude_mpm/services/version_service.py +104 -1
  259. claude_mpm/skills/__init__.py +21 -0
  260. claude_mpm/skills/bundled/__init__.py +6 -0
  261. claude_mpm/skills/bundled/api-documentation.md +393 -0
  262. claude_mpm/skills/bundled/async-testing.md +571 -0
  263. claude_mpm/skills/bundled/code-review.md +143 -0
  264. claude_mpm/skills/bundled/database-migration.md +199 -0
  265. claude_mpm/skills/bundled/docker-containerization.md +194 -0
  266. claude_mpm/skills/bundled/express-local-dev.md +1429 -0
  267. claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
  268. claude_mpm/skills/bundled/git-workflow.md +414 -0
  269. claude_mpm/skills/bundled/imagemagick.md +204 -0
  270. claude_mpm/skills/bundled/json-data-handling.md +223 -0
  271. claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
  272. claude_mpm/skills/bundled/pdf.md +141 -0
  273. claude_mpm/skills/bundled/performance-profiling.md +567 -0
  274. claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
  275. claude_mpm/skills/bundled/security-scanning.md +327 -0
  276. claude_mpm/skills/bundled/systematic-debugging.md +473 -0
  277. claude_mpm/skills/bundled/test-driven-development.md +378 -0
  278. claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
  279. claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
  280. claude_mpm/skills/bundled/xlsx.md +157 -0
  281. claude_mpm/skills/registry.py +286 -0
  282. claude_mpm/skills/skill_manager.py +310 -0
  283. claude_mpm/storage/state_storage.py +15 -15
  284. claude_mpm/tools/code_tree_analyzer.py +177 -141
  285. claude_mpm/tools/code_tree_events.py +4 -2
  286. claude_mpm/utils/agent_dependency_loader.py +40 -20
  287. claude_mpm/utils/display_helper.py +260 -0
  288. claude_mpm/utils/git_analyzer.py +407 -0
  289. claude_mpm/utils/robust_installer.py +73 -19
  290. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/METADATA +129 -12
  291. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/RECORD +295 -193
  292. claude_mpm/dashboard/static/css/code-tree.css +0 -1639
  293. claude_mpm/dashboard/static/index-hub-backup.html +0 -713
  294. claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +0 -353
  295. claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +0 -235
  296. claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +0 -409
  297. claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +0 -435
  298. claude_mpm/dashboard/static/js/components/code-tree.js +0 -5869
  299. claude_mpm/dashboard/static/js/components/code-viewer.js +0 -1386
  300. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +0 -425
  301. claude_mpm/hooks/claude_hooks/hook_handler_original.py +0 -1041
  302. claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +0 -347
  303. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +0 -575
  304. claude_mpm/services/project/analyzer_refactored.py +0 -450
  305. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/WHEEL +0 -0
  306. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/entry_points.txt +0 -0
  307. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/licenses/LICENSE +0 -0
  308. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/top_level.txt +0 -0
@@ -38,6 +38,8 @@ from enum import Enum
38
38
  from pathlib import Path
39
39
  from typing import Any, Dict, List, Optional, Tuple, Union
40
40
 
41
+ from claude_mpm.core.enums import AgentCategory
42
+
41
43
  # Module-level logger
42
44
  from claude_mpm.core.logging_utils import get_logger
43
45
 
@@ -208,7 +210,7 @@ class AgentLoader:
208
210
  self.registry.load_agents()
209
211
 
210
212
  init_time = (time.time() - start_time) * 1000
211
- logger.info(
213
+ logger.debug(
212
214
  f"AgentLoader initialized in {init_time:.2f}ms with {len(self.registry._agent_registry)} agents"
213
215
  )
214
216
 
@@ -281,11 +283,21 @@ class AgentLoader:
281
283
  # Check for project memory
282
284
  has_memory = capabilities.get("has_project_memory", False)
283
285
 
286
+ # Get category with enum validation (fallback to GENERAL if invalid)
287
+ category_str = metadata.get("category", "general")
288
+ try:
289
+ category = AgentCategory(category_str)
290
+ except ValueError:
291
+ logger.warning(
292
+ f"Invalid category '{category_str}' for agent {agent_id}, using GENERAL"
293
+ )
294
+ category = AgentCategory.GENERAL
295
+
284
296
  result = {
285
297
  "agent_id": agent_id,
286
298
  "name": metadata.get("name", agent_id),
287
299
  "description": metadata.get("description", ""),
288
- "category": metadata.get("category", "general"),
300
+ "category": category.value, # Store as string for backward compatibility
289
301
  "version": metadata.get("version", "1.0.0"),
290
302
  "model": agent_data.get("model", "claude-sonnet-4-20250514"),
291
303
  "resource_tier": agent_data.get("resource_tier", "standard"),
@@ -309,12 +321,12 @@ class AgentLoader:
309
321
  """
310
322
  Reload all agents from disk, clearing the registry.
311
323
  """
312
- logger.info("Reloading agent system...")
324
+ logger.debug("Reloading agent system...")
313
325
 
314
326
  # Reload registry
315
327
  self.registry.reload()
316
328
 
317
- logger.info(
329
+ logger.debug(
318
330
  f"Agent system reloaded with {len(self.registry._agent_registry)} agents"
319
331
  )
320
332
 
@@ -413,7 +425,7 @@ def reload_agents() -> None:
413
425
  # Clear the global instance to force reinitialization
414
426
  _loader = None
415
427
 
416
- logger.info("Agent registry cleared, will reload on next access")
428
+ logger.debug("Agent registry cleared, will reload on next access")
417
429
 
418
430
 
419
431
  def get_agent_tier(agent_name: str) -> Optional[str]:
@@ -23,6 +23,7 @@ from typing import Any, Dict, List, Optional, Tuple
23
23
 
24
24
  import yaml
25
25
 
26
+ from claude_mpm.core.enums import ModelTier
26
27
  from claude_mpm.core.logging_utils import get_logger
27
28
 
28
29
  logger = get_logger(__name__)
@@ -54,33 +55,8 @@ class FrontmatterValidator:
54
55
  - Logging of all corrections made
55
56
  """
56
57
 
57
- # Model name mappings for normalization
58
- MODEL_MAPPINGS = {
59
- # Sonnet variations
60
- "claude-3-5-sonnet-20241022": "sonnet",
61
- "claude-3-5-sonnet-20240620": "sonnet",
62
- "claude-sonnet-4-20250514": "sonnet",
63
- "claude-4-sonnet-20250514": "sonnet",
64
- "claude-3-sonnet-20240229": "sonnet",
65
- "20241022": "sonnet", # Common shorthand - maps to current Sonnet
66
- "20240620": "sonnet", # Previous Sonnet version
67
- "3.5-sonnet": "sonnet",
68
- "sonnet-3.5": "sonnet",
69
- "sonnet-4": "sonnet",
70
- # Opus variations
71
- "claude-3-opus-20240229": "opus",
72
- "claude-opus-4-20250514": "opus",
73
- "claude-4-opus-20250514": "opus",
74
- "3-opus": "opus",
75
- "opus-3": "opus",
76
- "opus-4": "opus",
77
- # Haiku variations
78
- "claude-3-haiku-20240307": "haiku",
79
- "claude-3-5-haiku-20241022": "haiku",
80
- "3-haiku": "haiku",
81
- "haiku-3": "haiku",
82
- "haiku-3.5": "haiku",
83
- }
58
+ # NOTE: Model normalization now handled by ModelTier.normalize()
59
+ # This enum-based approach replaced 26 lines of manual mappings
84
60
 
85
61
  # Tool name corrections (case normalization)
86
62
  TOOL_CORRECTIONS = {
@@ -179,13 +155,44 @@ class FrontmatterValidator:
179
155
  Returns:
180
156
  ValidationResult with validation status and corrected frontmatter
181
157
  """
182
- errors = []
183
- warnings = []
184
- corrections = []
158
+ errors: List[str] = []
159
+ warnings: List[str] = []
160
+ corrections: List[str] = []
185
161
  corrected = frontmatter.copy()
186
- field_corrections = {} # Track only the fields that actually need correction
162
+ field_corrections: Dict[str, Any] = {}
163
+
164
+ # Check required fields
165
+ self._validate_required_fields(corrected, errors)
166
+
167
+ # Validate and correct individual fields
168
+ self._validate_name_field(corrected, field_corrections, errors, corrections)
169
+ self._validate_model_field(corrected, field_corrections, errors, corrections)
170
+ self._validate_tools_field(corrected, field_corrections, warnings, corrections)
171
+ self._validate_version_fields(corrected, field_corrections, errors, corrections)
172
+ self._validate_description_field(corrected, errors, warnings)
173
+ self._validate_category_field(corrected, warnings)
174
+ self._validate_resource_tier_field(corrected, warnings)
175
+ self._validate_color_field(corrected, errors)
176
+ self._validate_author_field(corrected, errors, warnings)
177
+ self._validate_tags_field(corrected, errors, warnings)
178
+ self._validate_numeric_fields(corrected, errors, warnings)
179
+
180
+ # Determine if valid
181
+ is_valid = len(errors) == 0
182
+
183
+ return ValidationResult(
184
+ is_valid=is_valid,
185
+ errors=errors,
186
+ warnings=warnings,
187
+ corrections=corrections,
188
+ corrected_frontmatter=corrected if corrections else None,
189
+ field_corrections=field_corrections if field_corrections else None,
190
+ )
187
191
 
188
- # Required fields check (from schema)
192
+ def _validate_required_fields(
193
+ self, corrected: Dict[str, Any], errors: List[str]
194
+ ) -> None:
195
+ """Check that all required fields are present."""
189
196
  required_fields = (
190
197
  self.schema.get("required", ["name", "description", "version", "model"])
191
198
  if self.schema
@@ -195,227 +202,271 @@ class FrontmatterValidator:
195
202
  if field not in corrected:
196
203
  errors.append(f"Missing required field: {field}")
197
204
 
198
- # Validate and correct name field
199
- if "name" in corrected:
200
- name = corrected["name"]
201
- if not isinstance(name, str):
202
- errors.append(
203
- f"Field 'name' must be a string, got {type(name).__name__}"
204
- )
205
- elif not re.match(r"^[a-z][a-z0-9_]*$", name):
206
- # Try to fix the name
207
- fixed_name = name.lower().replace("-", "_").replace(" ", "_")
208
- fixed_name = re.sub(r"[^a-z0-9_]", "", fixed_name)
209
- if fixed_name and fixed_name[0].isalpha():
210
- corrected["name"] = fixed_name
211
- field_corrections["name"] = fixed_name
212
- corrections.append(
213
- f"Corrected name from '{name}' to '{fixed_name}'"
214
- )
215
- else:
216
- errors.append(f"Invalid name format: {name}")
217
-
218
- # Validate and correct model field
219
- if "model" in corrected:
220
- model = corrected["model"]
221
-
222
- # Convert to string if it's a number (YAML might parse dates as integers)
223
- if isinstance(model, (int, float)):
224
- model = str(model)
225
- corrected["model"] = model
226
- field_corrections["model"] = model
227
- corrections.append(f"Converted model from number to string: {model}")
228
-
229
- if not isinstance(model, str):
230
- errors.append(
231
- f"Field 'model' must be a string, got {type(model).__name__}"
232
- )
205
+ def _validate_name_field(
206
+ self,
207
+ corrected: Dict[str, Any],
208
+ field_corrections: Dict[str, Any],
209
+ errors: List[str],
210
+ corrections: List[str],
211
+ ) -> None:
212
+ """Validate and correct the name field."""
213
+ if "name" not in corrected:
214
+ return
215
+
216
+ name = corrected["name"]
217
+ if not isinstance(name, str):
218
+ errors.append(f"Field 'name' must be a string, got {type(name).__name__}")
219
+ return
220
+
221
+ if not re.match(r"^[a-z][a-z0-9_]*$", name):
222
+ # Try to fix the name
223
+ fixed_name = name.lower().replace("-", "_").replace(" ", "_")
224
+ fixed_name = re.sub(r"[^a-z0-9_]", "", fixed_name)
225
+ if fixed_name and fixed_name[0].isalpha():
226
+ corrected["name"] = fixed_name
227
+ field_corrections["name"] = fixed_name
228
+ corrections.append(f"Corrected name from '{name}' to '{fixed_name}'")
233
229
  else:
234
- normalized_model = self._normalize_model(model)
235
- if normalized_model != model:
236
- corrected["model"] = normalized_model
237
- field_corrections["model"] = normalized_model
238
- corrections.append(
239
- f"Normalized model from '{model}' to '{normalized_model}'"
240
- )
241
-
242
- if normalized_model not in self.VALID_MODELS:
243
- errors.append(
244
- f"Invalid model: {model} (normalized to {normalized_model})"
245
- )
246
-
247
- # Validate and correct tools field
248
- if "tools" in corrected:
249
- tools = corrected["tools"]
250
- corrected_tools, tool_corrections = self._correct_tools(tools)
251
- if tool_corrections:
252
- corrected["tools"] = corrected_tools
253
- field_corrections["tools"] = corrected_tools
254
- corrections.extend(tool_corrections)
255
-
256
- # Validate tool names
257
- invalid_tools = []
258
- for tool in corrected_tools:
259
- if tool not in self.VALID_TOOLS:
260
- # Try to correct the tool name
261
- corrected_tool = self.TOOL_CORRECTIONS.get(tool.lower())
262
- if corrected_tool:
263
- idx = corrected_tools.index(tool)
264
- corrected_tools[idx] = corrected_tool
265
- corrected["tools"] = corrected_tools
266
- field_corrections["tools"] = corrected_tools
267
- corrections.append(
268
- f"Corrected tool '{tool}' to '{corrected_tool}'"
269
- )
270
- else:
271
- invalid_tools.append(tool)
272
-
273
- if invalid_tools:
274
- warnings.append(f"Unknown tools: {', '.join(invalid_tools)}")
230
+ errors.append(f"Invalid name format: {name}")
231
+
232
+ def _validate_model_field(
233
+ self,
234
+ corrected: Dict[str, Any],
235
+ field_corrections: Dict[str, Any],
236
+ errors: List[str],
237
+ corrections: List[str],
238
+ ) -> None:
239
+ """Validate and correct the model field."""
240
+ if "model" not in corrected:
241
+ return
242
+
243
+ model = corrected["model"]
244
+
245
+ # Convert to string if it's a number (YAML might parse dates as integers)
246
+ if isinstance(model, (int, float)):
247
+ model = str(model)
248
+ corrected["model"] = model
249
+ field_corrections["model"] = model
250
+ corrections.append(f"Converted model from number to string: {model}")
251
+
252
+ if not isinstance(model, str):
253
+ errors.append(f"Field 'model' must be a string, got {type(model).__name__}")
254
+ return
255
+
256
+ normalized_model = self._normalize_model(model)
257
+ if normalized_model != model:
258
+ corrected["model"] = normalized_model
259
+ field_corrections["model"] = normalized_model
260
+ corrections.append(
261
+ f"Normalized model from '{model}' to '{normalized_model}'"
262
+ )
275
263
 
276
- # Validate version fields
264
+ if normalized_model not in self.VALID_MODELS:
265
+ errors.append(f"Invalid model: {model} (normalized to {normalized_model})")
266
+
267
+ def _validate_tools_field(
268
+ self,
269
+ corrected: Dict[str, Any],
270
+ field_corrections: Dict[str, Any],
271
+ warnings: List[str],
272
+ corrections: List[str],
273
+ ) -> None:
274
+ """Validate and correct the tools field."""
275
+ if "tools" not in corrected:
276
+ return
277
+
278
+ tools = corrected["tools"]
279
+ corrected_tools, tool_corrections = self._correct_tools(tools)
280
+ if tool_corrections:
281
+ corrected["tools"] = corrected_tools
282
+ field_corrections["tools"] = corrected_tools
283
+ corrections.extend(tool_corrections)
284
+
285
+ # Validate tool names
286
+ invalid_tools = []
287
+ for tool in corrected_tools:
288
+ if tool not in self.VALID_TOOLS:
289
+ # Try to correct the tool name
290
+ corrected_tool = self.TOOL_CORRECTIONS.get(tool.lower())
291
+ if corrected_tool:
292
+ idx = corrected_tools.index(tool)
293
+ corrected_tools[idx] = corrected_tool
294
+ corrected["tools"] = corrected_tools
295
+ field_corrections["tools"] = corrected_tools
296
+ corrections.append(f"Corrected tool '{tool}' to '{corrected_tool}'")
297
+ else:
298
+ invalid_tools.append(tool)
299
+
300
+ if invalid_tools:
301
+ warnings.append(f"Unknown tools: {', '.join(invalid_tools)}")
302
+
303
+ def _validate_version_fields(
304
+ self,
305
+ corrected: Dict[str, Any],
306
+ field_corrections: Dict[str, Any],
307
+ errors: List[str],
308
+ corrections: List[str],
309
+ ) -> None:
310
+ """Validate and correct version fields."""
277
311
  version_fields = ["version", "base_version"]
278
312
  for field in version_fields:
279
- if field in corrected:
280
- version = corrected[field]
281
- if not isinstance(version, str):
282
- errors.append(
283
- f"Field '{field}' must be a string, got {type(version).__name__}"
284
- )
285
- elif not re.match(r"^\d+\.\d+\.\d+$", version):
286
- # Try to fix common version issues
287
- if re.match(r"^\d+\.\d+$", version):
288
- fixed_version = f"{version}.0"
289
- corrected[field] = fixed_version
290
- field_corrections[field] = fixed_version
291
- corrections.append(
292
- f"Fixed {field} from '{version}' to '{fixed_version}'"
293
- )
294
- elif re.match(r"^v?\d+\.\d+\.\d+$", version):
295
- fixed_version = version.lstrip("v")
296
- corrected[field] = fixed_version
297
- field_corrections[field] = fixed_version
298
- corrections.append(
299
- f"Fixed {field} from '{version}' to '{fixed_version}'"
300
- )
301
- else:
302
- errors.append(f"Invalid {field} format: {version}")
313
+ if field not in corrected:
314
+ continue
303
315
 
304
- # Validate description
305
- if "description" in corrected:
306
- desc = corrected["description"]
307
- if not isinstance(desc, str):
316
+ version = corrected[field]
317
+ if not isinstance(version, str):
308
318
  errors.append(
309
- f"Field 'description' must be a string, got {type(desc).__name__}"
310
- )
311
- elif len(desc) < 10:
312
- warnings.append(
313
- f"Description too short ({len(desc)} chars, minimum 10)"
314
- )
315
- elif len(desc) > 200:
316
- warnings.append(
317
- f"Description too long ({len(desc)} chars, maximum 200)"
319
+ f"Field '{field}' must be a string, got {type(version).__name__}"
318
320
  )
321
+ continue
319
322
 
320
- # Validate optional fields
321
- if "category" in corrected:
322
- valid_categories = [
323
- "engineering",
324
- "research",
325
- "quality",
326
- "operations",
327
- "specialized",
328
- ]
329
- if corrected["category"] not in valid_categories:
330
- warnings.append(f"Invalid category: {corrected['category']}")
331
-
332
- if "resource_tier" in corrected:
333
- valid_tiers = ["basic", "standard", "intensive", "lightweight"]
334
- if corrected["resource_tier"] not in valid_tiers:
335
- warnings.append(f"Invalid resource_tier: {corrected['resource_tier']}")
336
-
337
- # Validate color field
338
- if "color" in corrected:
339
- color = corrected["color"]
340
- if not isinstance(color, str):
341
- errors.append(
342
- f"Field 'color' must be a string, got {type(color).__name__}"
343
- )
344
- # Color validation could be expanded to check for valid color names/hex codes
323
+ if re.match(r"^\d+\.\d+\.\d+$", version):
324
+ continue # Valid format
345
325
 
346
- # Validate author field
347
- if "author" in corrected:
348
- author = corrected["author"]
349
- if not isinstance(author, str):
350
- errors.append(
351
- f"Field 'author' must be a string, got {type(author).__name__}"
326
+ # Try to fix common version issues
327
+ if re.match(r"^\d+\.\d+$", version):
328
+ fixed_version = f"{version}.0"
329
+ corrected[field] = fixed_version
330
+ field_corrections[field] = fixed_version
331
+ corrections.append(
332
+ f"Fixed {field} from '{version}' to '{fixed_version}'"
352
333
  )
353
- elif len(author) > 100:
354
- warnings.append(
355
- f"Author field too long ({len(author)} chars, maximum 100)"
334
+ elif re.match(r"^v?\d+\.\d+\.\d+$", version):
335
+ fixed_version = version.lstrip("v")
336
+ corrected[field] = fixed_version
337
+ field_corrections[field] = fixed_version
338
+ corrections.append(
339
+ f"Fixed {field} from '{version}' to '{fixed_version}'"
356
340
  )
357
-
358
- # Validate tags field (supports both list and comma-separated string)
359
- if "tags" in corrected:
360
- tags = corrected["tags"]
361
- if isinstance(tags, str):
362
- # Convert comma-separated string to list for validation
363
- tag_list = [tag.strip() for tag in tags.split(",") if tag.strip()]
364
- elif isinstance(tags, list):
365
- tag_list = tags
366
341
  else:
367
- errors.append(
368
- f"Field 'tags' must be a list or comma-separated string, got {type(tags).__name__}"
342
+ errors.append(f"Invalid {field} format: {version}")
343
+
344
+ def _validate_description_field(
345
+ self, corrected: Dict[str, Any], errors: List[str], warnings: List[str]
346
+ ) -> None:
347
+ """Validate the description field."""
348
+ if "description" not in corrected:
349
+ return
350
+
351
+ desc = corrected["description"]
352
+ if not isinstance(desc, str):
353
+ errors.append(
354
+ f"Field 'description' must be a string, got {type(desc).__name__}"
355
+ )
356
+ elif len(desc) < 10:
357
+ warnings.append(f"Description too short ({len(desc)} chars, minimum 10)")
358
+ elif len(desc) > 200:
359
+ warnings.append(f"Description too long ({len(desc)} chars, maximum 200)")
360
+
361
+ def _validate_category_field(
362
+ self, corrected: Dict[str, Any], warnings: List[str]
363
+ ) -> None:
364
+ """Validate the category field."""
365
+ if "category" not in corrected:
366
+ return
367
+
368
+ valid_categories = [
369
+ "engineering",
370
+ "research",
371
+ "quality",
372
+ "operations",
373
+ "specialized",
374
+ ]
375
+ if corrected["category"] not in valid_categories:
376
+ warnings.append(f"Invalid category: {corrected['category']}")
377
+
378
+ def _validate_resource_tier_field(
379
+ self, corrected: Dict[str, Any], warnings: List[str]
380
+ ) -> None:
381
+ """Validate the resource_tier field."""
382
+ if "resource_tier" not in corrected:
383
+ return
384
+
385
+ valid_tiers = ["basic", "standard", "intensive", "lightweight"]
386
+ if corrected["resource_tier"] not in valid_tiers:
387
+ warnings.append(f"Invalid resource_tier: {corrected['resource_tier']}")
388
+
389
+ def _validate_color_field(
390
+ self, corrected: Dict[str, Any], errors: List[str]
391
+ ) -> None:
392
+ """Validate the color field."""
393
+ if "color" not in corrected:
394
+ return
395
+
396
+ color = corrected["color"]
397
+ if not isinstance(color, str):
398
+ errors.append(f"Field 'color' must be a string, got {type(color).__name__}")
399
+
400
+ def _validate_author_field(
401
+ self, corrected: Dict[str, Any], errors: List[str], warnings: List[str]
402
+ ) -> None:
403
+ """Validate the author field."""
404
+ if "author" not in corrected:
405
+ return
406
+
407
+ author = corrected["author"]
408
+ if not isinstance(author, str):
409
+ errors.append(
410
+ f"Field 'author' must be a string, got {type(author).__name__}"
411
+ )
412
+ elif len(author) > 100:
413
+ warnings.append(f"Author field too long ({len(author)} chars, maximum 100)")
414
+
415
+ def _validate_tags_field(
416
+ self, corrected: Dict[str, Any], errors: List[str], warnings: List[str]
417
+ ) -> None:
418
+ """Validate the tags field."""
419
+ if "tags" not in corrected:
420
+ return
421
+
422
+ tags = corrected["tags"]
423
+ if isinstance(tags, str):
424
+ # Convert comma-separated string to list for validation
425
+ tag_list = [tag.strip() for tag in tags.split(",") if tag.strip()]
426
+ elif isinstance(tags, list):
427
+ tag_list = tags
428
+ else:
429
+ errors.append(
430
+ f"Field 'tags' must be a list or comma-separated string, got {type(tags).__name__}"
431
+ )
432
+ return
433
+
434
+ for tag in tag_list:
435
+ if not isinstance(tag, str):
436
+ errors.append(f"All tags must be strings, found {type(tag).__name__}")
437
+ elif not re.match(r"^[a-z][a-z0-9-]*$", tag):
438
+ warnings.append(
439
+ f"Tag '{tag}' doesn't match recommended pattern (lowercase, alphanumeric with hyphens)"
369
440
  )
370
- tag_list = []
371
-
372
- for tag in tag_list:
373
- if not isinstance(tag, str):
374
- errors.append(
375
- f"All tags must be strings, found {type(tag).__name__}"
376
- )
377
- elif not re.match(r"^[a-z][a-z0-9-]*$", tag):
378
- warnings.append(
379
- f"Tag '{tag}' doesn't match recommended pattern (lowercase, alphanumeric with hyphens)"
380
- )
381
-
382
- # Validate numeric fields
441
+
442
+ def _validate_numeric_fields(
443
+ self, corrected: Dict[str, Any], errors: List[str], warnings: List[str]
444
+ ) -> None:
445
+ """Validate numeric fields (max_tokens, temperature)."""
383
446
  for field_name, (min_val, max_val) in [
384
447
  ("max_tokens", (1000, 200000)),
385
448
  ("temperature", (0, 1)),
386
449
  ]:
387
- if field_name in corrected:
388
- value = corrected[field_name]
389
- if field_name == "temperature" and not isinstance(value, (int, float)):
390
- errors.append(
391
- f"Field '{field_name}' must be a number, got {type(value).__name__}"
392
- )
393
- elif field_name == "max_tokens" and not isinstance(value, int):
394
- errors.append(
395
- f"Field '{field_name}' must be an integer, got {type(value).__name__}"
396
- )
397
- elif isinstance(value, (int, float)) and not (
398
- min_val <= value <= max_val
399
- ):
400
- warnings.append(
401
- f"Field '{field_name}' value {value} outside recommended range [{min_val}, {max_val}]"
402
- )
450
+ if field_name not in corrected:
451
+ continue
403
452
 
404
- # Determine if valid
405
- is_valid = len(errors) == 0
406
-
407
- return ValidationResult(
408
- is_valid=is_valid,
409
- errors=errors,
410
- warnings=warnings,
411
- corrections=corrections,
412
- corrected_frontmatter=corrected if corrections else None,
413
- field_corrections=field_corrections if field_corrections else None,
414
- )
453
+ value = corrected[field_name]
454
+ if field_name == "temperature" and not isinstance(value, (int, float)):
455
+ errors.append(
456
+ f"Field '{field_name}' must be a number, got {type(value).__name__}"
457
+ )
458
+ elif field_name == "max_tokens" and not isinstance(value, int):
459
+ errors.append(
460
+ f"Field '{field_name}' must be an integer, got {type(value).__name__}"
461
+ )
462
+ elif isinstance(value, (int, float)) and not (min_val <= value <= max_val):
463
+ warnings.append(
464
+ f"Field '{field_name}' value {value} outside recommended range [{min_val}, {max_val}]"
465
+ )
415
466
 
416
467
  def _normalize_model(self, model: str) -> str:
417
468
  """
418
- Normalize model name to standard tier (opus, sonnet, haiku).
469
+ Normalize model name to standard tier using ModelTier enum.
419
470
 
420
471
  Args:
421
472
  model: Original model name
@@ -423,27 +474,7 @@ class FrontmatterValidator:
423
474
  Returns:
424
475
  Normalized model tier name
425
476
  """
426
- # Direct mapping check
427
- if model in self.MODEL_MAPPINGS:
428
- return self.MODEL_MAPPINGS[model]
429
-
430
- # Already normalized
431
- if model in self.VALID_MODELS:
432
- return model
433
-
434
- # Try case-insensitive match
435
- model_lower = model.lower()
436
- if model_lower in self.VALID_MODELS:
437
- return model_lower
438
-
439
- # Check if model contains tier name
440
- for tier in self.VALID_MODELS:
441
- if tier in model_lower:
442
- return tier
443
-
444
- # Default to sonnet if unrecognized
445
- logger.warning(f"Unrecognized model '{model}', defaulting to 'sonnet'")
446
- return "sonnet"
477
+ return ModelTier.normalize(model).value
447
478
 
448
479
  def _correct_tools(self, tools: Any) -> Tuple[List[str], List[str]]:
449
480
  """