Validation result with 'valid', 'errors', 'warnings'. */ public static function validate( $file_slug ) { $content = AIDF_Generator::generate( $file_slug ); if ( false === $content ) { return array( 'valid' => false, 'errors' => array( __( 'File could not be generated.', 'ai-discovery-files' ) ), 'warnings' => array(), ); } $validators = array( 'llms-txt' => 'validate_llms_txt', 'ai-txt' => 'validate_ai_txt', 'ai-json' => 'validate_ai_json', 'identity-json' => 'validate_identity_json', ); if ( isset( $validators[ $file_slug ] ) ) { return self::{$validators[ $file_slug ]}( $content ); } return array( 'valid' => true, 'errors' => array(), 'warnings' => array(), ); } /** * Validate all active files and check cross-file consistency. * * @return array> */ public static function validate_all() { $settings = AIDF_Plugin::get_settings(); $active_files = isset( $settings['active_files'] ) ? (array) $settings['active_files'] : array(); $results = array(); foreach ( $active_files as $slug ) { $results[ $slug ] = self::validate( $slug ); } $results['_consistency'] = self::validate_consistency( $active_files ); return $results; } /** * Validate llms.txt (ADF-001). * * @param string $content File content. * @return array */ private static function validate_llms_txt( $content ) { $errors = array(); $warnings = array(); // Must have an H1 title. if ( ! preg_match( '/^# .+$/m', $content ) ) { $errors[] = __( 'llms.txt must begin with an H1 heading (# Title).', 'ai-discovery-files' ); } // Should have a blockquote description. if ( false === strpos( $content, '> ' ) ) { $warnings[] = __( 'llms.txt should include a blockquote description (> text).', 'ai-discovery-files' ); } // Should have a contact section. if ( false === stripos( $content, '## Contact' ) ) { $warnings[] = __( 'llms.txt should include a Contact section.', 'ai-discovery-files' ); } return array( 'valid' => empty( $errors ), 'errors' => $errors, 'warnings' => $warnings, ); } /** * Validate ai.txt (ADF-004). * * @param string $content File content. * @return array */ private static function validate_ai_txt( $content ) { $errors = array(); $warnings = array(); if ( false === stripos( $content, 'AI Usage' ) && false === stripos( $content, 'Usage Policy' ) ) { $errors[] = __( 'ai.txt must include an AI usage policy declaration.', 'ai-discovery-files' ); } return array( 'valid' => empty( $errors ), 'errors' => $errors, 'warnings' => $warnings, ); } /** * Validate ai.json (ADF-005). * * @param string $content File content. * @return array */ private static function validate_ai_json( $content ) { $errors = array(); $warnings = array(); $decoded = json_decode( $content, true ); if ( null === $decoded ) { $errors[] = __( 'ai.json contains invalid JSON.', 'ai-discovery-files' ); return array( 'valid' => false, 'errors' => $errors, 'warnings' => $warnings, ); } if ( ! isset( $decoded['permissions'] ) ) { $errors[] = __( 'ai.json must contain a "permissions" object.', 'ai-discovery-files' ); } return array( 'valid' => empty( $errors ), 'errors' => $errors, 'warnings' => $warnings, ); } /** * Validate identity.json (ADF-006). * * @param string $content File content. * @return array */ private static function validate_identity_json( $content ) { $errors = array(); $warnings = array(); $decoded = json_decode( $content, true ); if ( null === $decoded ) { $errors[] = __( 'identity.json contains invalid JSON.', 'ai-discovery-files' ); return array( 'valid' => false, 'errors' => $errors, 'warnings' => $warnings, ); } if ( ! isset( $decoded['name'] ) || empty( $decoded['name'] ) ) { $errors[] = __( 'identity.json must contain a "name" field.', 'ai-discovery-files' ); } if ( ! isset( $decoded['url'] ) || empty( $decoded['url'] ) ) { $errors[] = __( 'identity.json must contain a "url" field.', 'ai-discovery-files' ); } return array( 'valid' => empty( $errors ), 'errors' => $errors, 'warnings' => $warnings, ); } /** * Validate cross-file consistency. * * @param array $active_files Active file slugs. * @return array */ private static function validate_consistency( $active_files ) { $warnings = array(); $settings = AIDF_Plugin::get_settings(); $name = ! empty( $settings['business_name'] ) ? $settings['business_name'] : get_bloginfo( 'name' ); // Check essential tier. if ( ! in_array( 'llms-txt', $active_files, true ) ) { $warnings[] = __( 'llms.txt (Essential tier) is not enabled — this is the most important AI Discovery File.', 'ai-discovery-files' ); } if ( ! in_array( 'ai-txt', $active_files, true ) ) { $warnings[] = __( 'ai.txt (Essential tier) is not enabled — AI systems need to know your usage permissions.', 'ai-discovery-files' ); } // If ai-json is active, ai-txt should be too. if ( in_array( 'ai-json', $active_files, true ) && ! in_array( 'ai-txt', $active_files, true ) ) { $warnings[] = __( 'ai.json is enabled without ai.txt — both should be active for consistency.', 'ai-discovery-files' ); } // Check that business name is set. if ( empty( $name ) ) { $warnings[] = __( 'No business name configured — this is required for most AI Discovery Files.', 'ai-discovery-files' ); } return array( 'valid' => empty( $warnings ), 'errors' => array(), 'warnings' => $warnings, ); } /** * Get the overall status for a file type. * * @param string $file_slug File type slug. * @return string 'active', 'inactive', 'conflict', or 'error'. */ public static function get_status( $file_slug ) { $settings = AIDF_Plugin::get_settings(); $active_files = isset( $settings['active_files'] ) ? (array) $settings['active_files'] : array(); if ( ! in_array( $file_slug, $active_files, true ) ) { return 'inactive'; } if ( AIDF_Server::physical_file_exists( $file_slug ) ) { return 'conflict'; } $validation = self::validate( $file_slug ); if ( ! $validation['valid'] ) { return 'error'; } return 'active'; } }