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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +118 -0
- claude_mpm/agents/BASE_ENGINEER.md +286 -0
- claude_mpm/agents/BASE_PM.md +106 -1
- claude_mpm/agents/OUTPUT_STYLE.md +329 -11
- claude_mpm/agents/PM_INSTRUCTIONS.md +397 -459
- claude_mpm/agents/agent_loader.py +17 -5
- claude_mpm/agents/frontmatter_validator.py +284 -253
- claude_mpm/agents/templates/README.md +465 -0
- claude_mpm/agents/templates/agent-manager.json +4 -1
- claude_mpm/agents/templates/agentic-coder-optimizer.json +13 -3
- claude_mpm/agents/templates/api_qa.json +11 -2
- claude_mpm/agents/templates/circuit_breakers.md +638 -0
- claude_mpm/agents/templates/clerk-ops.json +12 -2
- claude_mpm/agents/templates/code_analyzer.json +8 -2
- claude_mpm/agents/templates/content-agent.json +358 -0
- claude_mpm/agents/templates/dart_engineer.json +15 -2
- claude_mpm/agents/templates/data_engineer.json +15 -2
- claude_mpm/agents/templates/documentation.json +10 -2
- claude_mpm/agents/templates/engineer.json +21 -1
- claude_mpm/agents/templates/gcp_ops_agent.json +12 -2
- claude_mpm/agents/templates/git_file_tracking.md +584 -0
- claude_mpm/agents/templates/golang_engineer.json +270 -0
- claude_mpm/agents/templates/imagemagick.json +4 -1
- claude_mpm/agents/templates/java_engineer.json +346 -0
- claude_mpm/agents/templates/local_ops_agent.json +1227 -6
- claude_mpm/agents/templates/memory_manager.json +4 -1
- claude_mpm/agents/templates/nextjs_engineer.json +141 -133
- claude_mpm/agents/templates/ops.json +12 -2
- claude_mpm/agents/templates/php-engineer.json +270 -174
- claude_mpm/agents/templates/pm_examples.md +474 -0
- claude_mpm/agents/templates/pm_red_flags.md +240 -0
- claude_mpm/agents/templates/product_owner.json +338 -0
- claude_mpm/agents/templates/project_organizer.json +14 -4
- claude_mpm/agents/templates/prompt-engineer.json +13 -2
- claude_mpm/agents/templates/python_engineer.json +174 -81
- claude_mpm/agents/templates/qa.json +11 -2
- claude_mpm/agents/templates/react_engineer.json +16 -3
- claude_mpm/agents/templates/refactoring_engineer.json +12 -2
- claude_mpm/agents/templates/research.json +34 -21
- claude_mpm/agents/templates/response_format.md +583 -0
- claude_mpm/agents/templates/ruby-engineer.json +129 -192
- claude_mpm/agents/templates/rust_engineer.json +270 -0
- claude_mpm/agents/templates/security.json +10 -2
- claude_mpm/agents/templates/svelte-engineer.json +225 -0
- claude_mpm/agents/templates/ticketing.json +10 -2
- claude_mpm/agents/templates/typescript_engineer.json +116 -125
- claude_mpm/agents/templates/validation_templates.md +312 -0
- claude_mpm/agents/templates/vercel_ops_agent.json +12 -2
- claude_mpm/agents/templates/version_control.json +12 -2
- claude_mpm/agents/templates/web_qa.json +11 -2
- claude_mpm/agents/templates/web_ui.json +15 -2
- claude_mpm/cli/__init__.py +34 -614
- claude_mpm/cli/commands/agent_manager.py +25 -12
- claude_mpm/cli/commands/agent_state_manager.py +186 -0
- claude_mpm/cli/commands/agents.py +235 -148
- claude_mpm/cli/commands/agents_detect.py +380 -0
- claude_mpm/cli/commands/agents_recommend.py +309 -0
- claude_mpm/cli/commands/aggregate.py +7 -3
- claude_mpm/cli/commands/analyze.py +9 -4
- claude_mpm/cli/commands/analyze_code.py +7 -2
- claude_mpm/cli/commands/auto_configure.py +570 -0
- claude_mpm/cli/commands/config.py +47 -13
- claude_mpm/cli/commands/configure.py +419 -1571
- claude_mpm/cli/commands/configure_agent_display.py +261 -0
- claude_mpm/cli/commands/configure_behavior_manager.py +204 -0
- claude_mpm/cli/commands/configure_hook_manager.py +225 -0
- claude_mpm/cli/commands/configure_models.py +18 -0
- claude_mpm/cli/commands/configure_navigation.py +167 -0
- claude_mpm/cli/commands/configure_paths.py +104 -0
- claude_mpm/cli/commands/configure_persistence.py +254 -0
- claude_mpm/cli/commands/configure_startup_manager.py +646 -0
- claude_mpm/cli/commands/configure_template_editor.py +497 -0
- claude_mpm/cli/commands/configure_validators.py +73 -0
- claude_mpm/cli/commands/local_deploy.py +537 -0
- claude_mpm/cli/commands/memory.py +54 -20
- claude_mpm/cli/commands/mpm_init.py +585 -196
- claude_mpm/cli/commands/mpm_init_handler.py +37 -3
- claude_mpm/cli/commands/search.py +170 -4
- claude_mpm/cli/commands/upgrade.py +152 -0
- claude_mpm/cli/executor.py +202 -0
- claude_mpm/cli/helpers.py +105 -0
- claude_mpm/cli/interactive/__init__.py +3 -0
- claude_mpm/cli/interactive/skills_wizard.py +491 -0
- claude_mpm/cli/parsers/__init__.py +7 -1
- claude_mpm/cli/parsers/agents_parser.py +9 -0
- claude_mpm/cli/parsers/auto_configure_parser.py +245 -0
- claude_mpm/cli/parsers/base_parser.py +110 -3
- claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
- claude_mpm/cli/parsers/mpm_init_parser.py +65 -5
- claude_mpm/cli/shared/output_formatters.py +28 -19
- claude_mpm/cli/startup.py +481 -0
- claude_mpm/cli/utils.py +52 -1
- claude_mpm/commands/mpm-agents-detect.md +168 -0
- claude_mpm/commands/mpm-agents-recommend.md +214 -0
- claude_mpm/commands/mpm-agents.md +75 -1
- claude_mpm/commands/mpm-auto-configure.md +217 -0
- claude_mpm/commands/mpm-help.md +163 -0
- claude_mpm/commands/mpm-init.md +148 -3
- claude_mpm/commands/mpm-version.md +113 -0
- claude_mpm/commands/mpm.md +1 -0
- claude_mpm/config/agent_config.py +2 -2
- claude_mpm/config/model_config.py +428 -0
- claude_mpm/constants.py +1 -0
- claude_mpm/core/base_service.py +13 -12
- claude_mpm/core/enums.py +452 -0
- claude_mpm/core/factories.py +1 -1
- claude_mpm/core/instruction_reinforcement_hook.py +2 -1
- claude_mpm/core/interactive_session.py +9 -3
- claude_mpm/core/log_manager.py +2 -0
- claude_mpm/core/logging_config.py +6 -2
- claude_mpm/core/oneshot_session.py +8 -4
- claude_mpm/core/optimized_agent_loader.py +3 -3
- claude_mpm/core/output_style_manager.py +12 -192
- claude_mpm/core/service_registry.py +5 -1
- claude_mpm/core/types.py +2 -9
- claude_mpm/core/typing_utils.py +7 -6
- claude_mpm/dashboard/static/js/dashboard.js +0 -14
- claude_mpm/dashboard/templates/index.html +3 -41
- claude_mpm/hooks/__init__.py +20 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +4 -2
- claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +23 -2
- claude_mpm/hooks/failure_learning/__init__.py +60 -0
- claude_mpm/hooks/failure_learning/failure_detection_hook.py +235 -0
- claude_mpm/hooks/failure_learning/fix_detection_hook.py +217 -0
- claude_mpm/hooks/failure_learning/learning_extraction_hook.py +286 -0
- claude_mpm/hooks/instruction_reinforcement.py +7 -2
- claude_mpm/hooks/kuzu_enrichment_hook.py +263 -0
- claude_mpm/hooks/kuzu_memory_hook.py +37 -12
- claude_mpm/hooks/kuzu_response_hook.py +183 -0
- claude_mpm/models/resume_log.py +340 -0
- claude_mpm/services/agents/__init__.py +18 -5
- claude_mpm/services/agents/auto_config_manager.py +796 -0
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
- claude_mpm/services/agents/deployment/agent_record_service.py +1 -1
- claude_mpm/services/agents/deployment/agent_validator.py +17 -1
- claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
- claude_mpm/services/agents/deployment/interface_adapter.py +3 -2
- claude_mpm/services/agents/deployment/local_template_deployment.py +1 -1
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +7 -6
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +7 -16
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +4 -3
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +5 -3
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +6 -5
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +9 -6
- claude_mpm/services/agents/deployment/validation/__init__.py +3 -1
- claude_mpm/services/agents/deployment/validation/validation_result.py +1 -9
- claude_mpm/services/agents/local_template_manager.py +1 -1
- claude_mpm/services/agents/memory/agent_memory_manager.py +5 -2
- claude_mpm/services/agents/observers.py +547 -0
- claude_mpm/services/agents/recommender.py +568 -0
- claude_mpm/services/agents/registry/modification_tracker.py +5 -2
- claude_mpm/services/command_handler_service.py +11 -5
- claude_mpm/services/core/__init__.py +33 -1
- claude_mpm/services/core/interfaces/__init__.py +90 -3
- claude_mpm/services/core/interfaces/agent.py +184 -0
- claude_mpm/services/core/interfaces/health.py +172 -0
- claude_mpm/services/core/interfaces/model.py +281 -0
- claude_mpm/services/core/interfaces/process.py +372 -0
- claude_mpm/services/core/interfaces/project.py +121 -0
- claude_mpm/services/core/interfaces/restart.py +307 -0
- claude_mpm/services/core/interfaces/stability.py +260 -0
- claude_mpm/services/core/memory_manager.py +11 -24
- claude_mpm/services/core/models/__init__.py +79 -0
- claude_mpm/services/core/models/agent_config.py +381 -0
- claude_mpm/services/core/models/health.py +162 -0
- claude_mpm/services/core/models/process.py +235 -0
- claude_mpm/services/core/models/restart.py +302 -0
- claude_mpm/services/core/models/stability.py +264 -0
- claude_mpm/services/core/models/toolchain.py +306 -0
- claude_mpm/services/core/path_resolver.py +23 -7
- claude_mpm/services/diagnostics/__init__.py +2 -2
- claude_mpm/services/diagnostics/checks/agent_check.py +25 -24
- claude_mpm/services/diagnostics/checks/claude_code_check.py +24 -23
- claude_mpm/services/diagnostics/checks/common_issues_check.py +25 -24
- claude_mpm/services/diagnostics/checks/configuration_check.py +24 -23
- claude_mpm/services/diagnostics/checks/filesystem_check.py +18 -17
- claude_mpm/services/diagnostics/checks/installation_check.py +30 -29
- claude_mpm/services/diagnostics/checks/instructions_check.py +20 -19
- claude_mpm/services/diagnostics/checks/mcp_check.py +50 -36
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +38 -33
- claude_mpm/services/diagnostics/checks/monitor_check.py +23 -22
- claude_mpm/services/diagnostics/checks/startup_log_check.py +9 -8
- claude_mpm/services/diagnostics/diagnostic_runner.py +6 -5
- claude_mpm/services/diagnostics/doctor_reporter.py +28 -25
- claude_mpm/services/diagnostics/models.py +19 -24
- claude_mpm/services/infrastructure/monitoring/__init__.py +1 -1
- claude_mpm/services/infrastructure/monitoring/aggregator.py +12 -12
- claude_mpm/services/infrastructure/monitoring/base.py +5 -13
- claude_mpm/services/infrastructure/monitoring/network.py +7 -6
- claude_mpm/services/infrastructure/monitoring/process.py +13 -12
- claude_mpm/services/infrastructure/monitoring/resources.py +7 -6
- claude_mpm/services/infrastructure/monitoring/service.py +16 -15
- claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
- claude_mpm/services/local_ops/__init__.py +163 -0
- claude_mpm/services/local_ops/crash_detector.py +257 -0
- claude_mpm/services/local_ops/health_checks/__init__.py +28 -0
- claude_mpm/services/local_ops/health_checks/http_check.py +224 -0
- claude_mpm/services/local_ops/health_checks/process_check.py +236 -0
- claude_mpm/services/local_ops/health_checks/resource_check.py +255 -0
- claude_mpm/services/local_ops/health_manager.py +430 -0
- claude_mpm/services/local_ops/log_monitor.py +396 -0
- claude_mpm/services/local_ops/memory_leak_detector.py +294 -0
- claude_mpm/services/local_ops/process_manager.py +595 -0
- claude_mpm/services/local_ops/resource_monitor.py +331 -0
- claude_mpm/services/local_ops/restart_manager.py +401 -0
- claude_mpm/services/local_ops/restart_policy.py +387 -0
- claude_mpm/services/local_ops/state_manager.py +372 -0
- claude_mpm/services/local_ops/unified_manager.py +600 -0
- claude_mpm/services/mcp_config_manager.py +9 -4
- claude_mpm/services/mcp_gateway/core/__init__.py +1 -2
- claude_mpm/services/mcp_gateway/core/base.py +18 -31
- claude_mpm/services/mcp_gateway/main.py +30 -0
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +206 -32
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +30 -28
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +25 -5
- claude_mpm/services/mcp_service_verifier.py +1 -1
- claude_mpm/services/memory/failure_tracker.py +563 -0
- claude_mpm/services/memory_hook_service.py +165 -4
- claude_mpm/services/model/__init__.py +147 -0
- claude_mpm/services/model/base_provider.py +365 -0
- claude_mpm/services/model/claude_provider.py +412 -0
- claude_mpm/services/model/model_router.py +453 -0
- claude_mpm/services/model/ollama_provider.py +415 -0
- claude_mpm/services/monitor/daemon_manager.py +3 -2
- claude_mpm/services/monitor/handlers/dashboard.py +2 -1
- claude_mpm/services/monitor/handlers/hooks.py +2 -1
- claude_mpm/services/monitor/management/lifecycle.py +3 -2
- claude_mpm/services/monitor/server.py +2 -1
- claude_mpm/services/project/__init__.py +23 -0
- claude_mpm/services/project/detection_strategies.py +719 -0
- claude_mpm/services/project/toolchain_analyzer.py +581 -0
- claude_mpm/services/self_upgrade_service.py +342 -0
- claude_mpm/services/session_management_service.py +3 -2
- claude_mpm/services/session_manager.py +205 -1
- claude_mpm/services/shared/async_service_base.py +16 -27
- claude_mpm/services/shared/lifecycle_service_base.py +1 -14
- claude_mpm/services/socketio/handlers/__init__.py +5 -2
- claude_mpm/services/socketio/handlers/hook.py +13 -2
- claude_mpm/services/socketio/handlers/registry.py +4 -2
- claude_mpm/services/socketio/server/main.py +10 -8
- claude_mpm/services/subprocess_launcher_service.py +14 -5
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +8 -7
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +6 -5
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +8 -7
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +7 -6
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +5 -4
- claude_mpm/services/unified/config_strategies/validation_strategy.py +13 -9
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +10 -3
- claude_mpm/services/unified/deployment_strategies/local.py +6 -5
- claude_mpm/services/unified/deployment_strategies/utils.py +6 -5
- claude_mpm/services/unified/deployment_strategies/vercel.py +7 -6
- claude_mpm/services/unified/interfaces.py +3 -1
- claude_mpm/services/unified/unified_analyzer.py +14 -10
- claude_mpm/services/unified/unified_config.py +2 -1
- claude_mpm/services/unified/unified_deployment.py +9 -4
- claude_mpm/services/version_service.py +104 -1
- claude_mpm/skills/__init__.py +21 -0
- claude_mpm/skills/bundled/__init__.py +6 -0
- claude_mpm/skills/bundled/api-documentation.md +393 -0
- claude_mpm/skills/bundled/async-testing.md +571 -0
- claude_mpm/skills/bundled/code-review.md +143 -0
- claude_mpm/skills/bundled/database-migration.md +199 -0
- claude_mpm/skills/bundled/docker-containerization.md +194 -0
- claude_mpm/skills/bundled/express-local-dev.md +1429 -0
- claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
- claude_mpm/skills/bundled/git-workflow.md +414 -0
- claude_mpm/skills/bundled/imagemagick.md +204 -0
- claude_mpm/skills/bundled/json-data-handling.md +223 -0
- claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
- claude_mpm/skills/bundled/pdf.md +141 -0
- claude_mpm/skills/bundled/performance-profiling.md +567 -0
- claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
- claude_mpm/skills/bundled/security-scanning.md +327 -0
- claude_mpm/skills/bundled/systematic-debugging.md +473 -0
- claude_mpm/skills/bundled/test-driven-development.md +378 -0
- claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
- claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
- claude_mpm/skills/bundled/xlsx.md +157 -0
- claude_mpm/skills/registry.py +286 -0
- claude_mpm/skills/skill_manager.py +310 -0
- claude_mpm/storage/state_storage.py +15 -15
- claude_mpm/tools/code_tree_analyzer.py +177 -141
- claude_mpm/tools/code_tree_events.py +4 -2
- claude_mpm/utils/agent_dependency_loader.py +40 -20
- claude_mpm/utils/display_helper.py +260 -0
- claude_mpm/utils/git_analyzer.py +407 -0
- claude_mpm/utils/robust_installer.py +73 -19
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/METADATA +129 -12
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/RECORD +295 -193
- claude_mpm/dashboard/static/css/code-tree.css +0 -1639
- claude_mpm/dashboard/static/index-hub-backup.html +0 -713
- 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/hook_handler_eventbus.py +0 -425
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +0 -1041
- claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +0 -347
- claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +0 -575
- claude_mpm/services/project/analyzer_refactored.py +0 -450
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/WHEEL +0 -0
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/licenses/LICENSE +0 -0
- {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.
|
|
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":
|
|
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.
|
|
324
|
+
logger.debug("Reloading agent system...")
|
|
313
325
|
|
|
314
326
|
# Reload registry
|
|
315
327
|
self.registry.reload()
|
|
316
328
|
|
|
317
|
-
logger.
|
|
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.
|
|
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
|
|
58
|
-
|
|
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 = {}
|
|
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
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
305
|
-
|
|
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 '
|
|
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
|
-
|
|
321
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
|
|
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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
|
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
|
-
|
|
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
|
"""
|