#!/usr/bin/env php $match) { $ability_name = $match[1]; $args_string = $match[2]; echo "Validating ability: {$ability_name}\n"; // Validate ability name format if (!preg_match('/^[a-z0-9-]+\/[a-z0-9-]+$/', $ability_name)) { $errors[] = "Invalid ability name format: '{$ability_name}'. Must be 'namespace/ability-name' (lowercase, hyphens allowed)"; } // Parse the arguments array $args = parse_ability_args($args_string); // Validate required fields validate_required_fields($ability_name, $args, $errors, $warnings); // Validate schemas if (isset($args['input_schema'])) { validate_json_schema($ability_name, 'input_schema', $args['input_schema'], $errors); } if (isset($args['output_schema'])) { validate_json_schema($ability_name, 'output_schema', $args['output_schema'], $errors); } // Best practice checks check_best_practices($ability_name, $args, $warnings); echo "\n"; } // Output final results output_results($file_path, $errors, $warnings); // Exit with appropriate code exit(empty($errors) ? 0 : 1); /** * Parse ability registration arguments from string. * * This is a simplified parser that extracts key configuration values. * * @param string $args_string The arguments array as a string * @return array Parsed arguments */ function parse_ability_args($args_string) { $args = []; // Extract simple string/boolean values $simple_patterns = [ 'label' => '/[\'"]label[\'"]\s*=>\s*(?:__\s*\(\s*)?[\'"]([^\'"]+)[\'"]/', 'description' => '/[\'"]description[\'"]\s*=>\s*(?:__\s*\(\s*)?[\'"]([^\'"]+)[\'"]/', 'category' => '/[\'"]category[\'"]\s*=>\s*[\'"]([^\'"]+)[\'"]/', 'execute_callback' => '/[\'"]execute_callback[\'"]\s*=>\s*[\'"]([^\'"]+)[\'"]/', ]; foreach ($simple_patterns as $key => $pattern) { if (preg_match($pattern, $args_string, $matches)) { $args[$key] = $matches[1]; } } // Check for permission_callback (function or string) if (preg_match('/[\'"]permission_callback[\'"]\s*=>\s*(.+?),?\s*(?=[\'"][a-z_]+[\'"]|$)/s', $args_string, $matches)) { $args['permission_callback'] = trim($matches[1]); } // Extract schema arrays (look for 'input_schema' and 'output_schema') if (preg_match('/[\'"]input_schema[\'"]\s*=>\s*array\s*\((.*?)\),?\s*(?=[\'"][a-z_]+[\'"]|$)/s', $args_string, $matches)) { $args['input_schema'] = $matches[1]; } if (preg_match('/[\'"]output_schema[\'"]\s*=>\s*array\s*\((.*?)\),?\s*(?=[\'"][a-z_]+[\'"]|$)/s', $args_string, $matches)) { $args['output_schema'] = $matches[1]; } // Extract meta array if (preg_match('/[\'"]meta[\'"]\s*=>\s*array\s*\((.*?)\),?\s*(?:\)|$)/s', $args_string, $matches)) { $args['meta'] = $matches[1]; } return $args; } /** * Validate required fields are present. * * @param string $ability_name Ability name * @param array $args Parsed arguments * @param array &$errors Error messages array * @param array &$warnings Warning messages array */ function validate_required_fields($ability_name, $args, &$errors, &$warnings) { $required_fields = ['label', 'description', 'category', 'execute_callback']; foreach ($required_fields as $field) { if (!isset($args[$field])) { $errors[] = "Missing required field '{$field}' in ability '{$ability_name}'"; } } // Check for empty descriptions (common mistake) if (isset($args['description']) && strlen(trim($args['description'])) < 10) { $warnings[] = "Description for '{$ability_name}' is too short. Provide detailed information for AI agents."; } // Check for placeholder/TODO descriptions if (isset($args['description']) && stripos($args['description'], 'TODO') !== false) { $warnings[] = "Description for '{$ability_name}' contains TODO placeholder. Replace with actual description."; } // Warn about missing schemas (recommended if ability takes input or provides output) if (!isset($args['input_schema'])) { $warnings[] = "Missing 'input_schema' in ability '{$ability_name}'. Input schema should be provided if this ability accepts parameters."; } if (!isset($args['output_schema'])) { $warnings[] = "Missing 'output_schema' in ability '{$ability_name}'. Output schema should be provided if this ability returns data."; } // Warn about missing permission callback if (!isset($args['permission_callback'])) { $warnings[] = "Missing 'permission_callback' in ability '{$ability_name}'. Consider adding permission checks to control who can execute this ability."; } } /** * Validate a JSON Schema structure. * * @param string $ability_name Ability name * @param string $schema_type Schema type ('input_schema' or 'output_schema') * @param string $schema_string Schema as string * @param array &$errors Error messages array */ function validate_json_schema($ability_name, $schema_type, $schema_string, &$errors) { // Check for 'type' field (required in JSON Schema) if (strpos($schema_string, "'type'") === false && strpos($schema_string, '"type"') === false) { $errors[] = "{$schema_type} for '{$ability_name}' missing 'type' field (required by JSON Schema)"; } // Check for 'properties' if type is 'object' if (preg_match('/[\'"]type[\'"]\s*=>\s*[\'"]object[\'"]/', $schema_string)) { if (strpos($schema_string, "'properties'") === false && strpos($schema_string, '"properties"') === false) { $errors[] = "{$schema_type} for '{$ability_name}' has type 'object' but missing 'properties' field"; } } // Check for 'items' if type is 'array' if (preg_match('/[\'"]type[\'"]\s*=>\s*[\'"]array[\'"]/', $schema_string)) { if (strpos($schema_string, "'items'") === false && strpos($schema_string, '"items"') === false) { $warnings[] = "{$schema_type} for '{$ability_name}' has type 'array' but missing 'items' field (recommended)"; } } // Validate that properties have types if (preg_match_all('/[\'"]properties[\'"]\s*=>\s*array\s*\((.*?)\)/s', $schema_string, $prop_matches)) { foreach ($prop_matches[1] as $properties) { // Extract each property definition if (preg_match_all('/[\'"]([a-z_]+)[\'"]\s*=>\s*array\s*\((.*?)\)/s', $properties, $prop_defs, PREG_SET_ORDER)) { foreach ($prop_defs as $prop_def) { $prop_name = $prop_def[1]; $prop_content = $prop_def[2]; // Each property should have a 'type' if (strpos($prop_content, "'type'") === false && strpos($prop_content, '"type"') === false) { $errors[] = "{$schema_type} property '{$prop_name}' in '{$ability_name}' missing 'type' field"; } } } } } } /** * Check for best practices. * * @param string $ability_name Ability name * @param array $args Parsed arguments * @param array &$warnings Warning messages array */ function check_best_practices($ability_name, $args, &$warnings) { // Check for __return_true permission callback (context-aware security check) if (isset($args['permission_callback']) && strpos($args['permission_callback'], '__return_true') !== false) { // Extract annotations to determine if this is a safe public ability $is_readonly = false; $is_destructive = true; if (isset($args['meta']) && strpos($args['meta'], 'annotations') !== false) { $is_readonly = preg_match('/[\'"]readonly[\'"]\s*=>\s*true/', $args['meta']); $is_destructive = !preg_match('/[\'"]destructive[\'"]\s*=>\s*false/', $args['meta']); } // Only warn if this is potentially dangerous (not readonly OR is destructive) if (!$is_readonly || $is_destructive) { $warnings[] = "Ability '{$ability_name}' uses __return_true() for permissions with write/destructive operations. Ensure public access is intentional."; } // If it's readonly and non-destructive, __return_true is fine - no warning needed } // Check for annotations in meta if (isset($args['meta'])) { $has_annotations = strpos($args['meta'], 'annotations') !== false; if (!$has_annotations) { $warnings[] = "Ability '{$ability_name}' missing annotations (readonly, destructive, idempotent). These help AI agents understand the ability's behavior."; } } else { $warnings[] = "Ability '{$ability_name}' missing 'meta' array. Consider adding annotations for better discoverability."; } // Check category naming (should be kebab-case) if (isset($args['category']) && !preg_match('/^[a-z0-9-]+$/', $args['category'])) { $warnings[] = "Category '{$args['category']}' should use kebab-case naming (lowercase with hyphens)"; } } /** * Output validation results. * * @param string $file_path File being validated * @param array $errors Error messages * @param array $warnings Warning messages */ function output_results($file_path, $errors, $warnings) { echo str_repeat('=', 70) . "\n"; echo "Validation Results: {$file_path}\n"; echo str_repeat('=', 70) . "\n\n"; if (!empty($errors)) { echo "ERRORS (" . count($errors) . "):\n"; foreach ($errors as $error) { echo " ✗ {$error}\n"; } echo "\n"; } if (!empty($warnings)) { echo "WARNINGS (" . count($warnings) . "):\n"; foreach ($warnings as $warning) { echo " ⚠ {$warning}\n"; } echo "\n"; } if (empty($errors) && empty($warnings)) { echo "✓ All validations passed! No issues found.\n\n"; } elseif (empty($errors)) { echo "✓ Validation passed with warnings.\n\n"; } else { echo "✗ Validation failed. Please fix the errors above.\n\n"; } }