From 2fb3c576fbc04f2ffbef07ddbbab84d0231f8e33 Mon Sep 17 00:00:00 2001
From: Ishita Jagtap <143935560+ishitaj34@users.noreply.github.com>
Date: Thu, 9 Jul 2026 19:41:20 +0530
Subject: [PATCH 01/11] feat: integrate Plugin Check Namer as standard AI Name
Check static check
---
assets/js/plugin-check-namer.js | 432 ----------------
includes/Admin/Namer_Page.php | 460 ------------------
includes/Checker/Abstract_Check_Runner.php | 13 +-
.../Checks/Plugin_Repo/AI_Name_Check.php | 179 +++++++
includes/Checker/Default_Check_Repository.php | 1 +
includes/Plugin_Main.php | 8 +-
.../Checker/Checks/AI_Name_Check_Tests.php | 33 ++
7 files changed, 228 insertions(+), 898 deletions(-)
delete mode 100644 assets/js/plugin-check-namer.js
delete mode 100644 includes/Admin/Namer_Page.php
create mode 100644 includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php
create mode 100644 tests/phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php
diff --git a/assets/js/plugin-check-namer.js b/assets/js/plugin-check-namer.js
deleted file mode 100644
index f8e3d90cb..000000000
--- a/assets/js/plugin-check-namer.js
+++ /dev/null
@@ -1,432 +0,0 @@
-/**
- * Plugin Check Namer tool.
- */
-
-/* global pluginCheckNamer */
-
-( function () {
- 'use strict';
-
- function setText( el, text ) {
- if ( ! el ) {
- return;
- }
- el.textContent = text;
- }
-
- function setHtml( el, html ) {
- if ( ! el ) {
- return;
- }
- el.innerHTML = html;
- }
-
- function escapeHtml( text ) {
- const div = document.createElement( 'div' );
- div.textContent = text;
- return div.innerHTML;
- }
-
- document.addEventListener( 'DOMContentLoaded', function () {
- const form = document.getElementById( 'plugin-check-namer-form' );
- const input = document.getElementById( 'plugin_check_namer_input' );
-
- if ( ! form || ! input || ! window.pluginCheckNamer ) {
- return;
- }
-
- const authorInput = document.getElementById(
- 'plugin_check_namer_author'
- );
- const modelSelect = document.getElementById(
- 'plugin_check_namer_model'
- );
-
- const submitBtn = document.getElementById(
- 'plugin-check-namer-submit'
- );
- const spinner = document.getElementById( 'plugin-check-namer-spinner' );
-
- const resultWrap = document.getElementById(
- 'plugin-check-namer-result'
- );
- const verdictEl = document.getElementById(
- 'plugin-check-namer-verdict'
- );
- const explainEl = document.getElementById(
- 'plugin-check-namer-explanation'
- );
- const verdictContainer = document.getElementById(
- 'plugin-check-namer-verdict-container'
- );
- const confusionPluginsDiv = document.getElementById(
- 'plugin-check-namer-confusion-plugins'
- );
- const confusionPluginsList = document.getElementById(
- 'plugin-check-namer-confusion-plugins-list'
- );
- const confusionOthersDiv = document.getElementById(
- 'plugin-check-namer-confusion-others'
- );
- const confusionOthersList = document.getElementById(
- 'plugin-check-namer-confusion-others-list'
- );
- const timingDiv = document.getElementById(
- 'plugin-check-namer-timing'
- );
- const timingValue = document.getElementById(
- 'plugin-check-namer-timing-value'
- );
- const tokensDiv = document.getElementById(
- 'plugin-check-namer-tokens'
- );
- const tokensValue = document.getElementById(
- 'plugin-check-namer-tokens-value'
- );
- const errorDiv = document.getElementById( 'plugin-check-namer-error' );
- const errorEl = errorDiv ? errorDiv.querySelector( 'p' ) : null;
-
- function setLoading( isLoading ) {
- if ( spinner ) {
- spinner.classList.toggle( 'is-active', isLoading );
- }
- submitBtn.disabled = isLoading;
- }
-
- form.addEventListener( 'submit', function ( event ) {
- event.preventDefault();
-
- const name = ( input.value || '' ).trim();
- if ( ! name ) {
- setText( errorEl, pluginCheckNamer.messages.missingName );
- if ( errorEl ) {
- errorEl.classList.remove( 'plugin-check-namer-hidden' );
- }
- return;
- }
-
- // Clear previous results.
- if ( resultWrap ) {
- resultWrap.classList.add( 'plugin-check-namer-hidden' );
- }
- if ( verdictEl ) {
- setText( verdictEl, '' );
- }
- if ( explainEl ) {
- setHtml( explainEl, '' );
- }
- if ( errorDiv ) {
- errorDiv.classList.add( 'plugin-check-namer-hidden' );
- }
- if ( errorEl ) {
- setText( errorEl, '' );
- }
- if ( confusionPluginsDiv ) {
- confusionPluginsDiv.classList.add(
- 'plugin-check-namer-hidden'
- );
- }
- if ( confusionPluginsList ) {
- confusionPluginsList.innerHTML = '';
- }
- if ( confusionOthersDiv ) {
- confusionOthersDiv.classList.add( 'plugin-check-namer-hidden' );
- }
- if ( confusionOthersList ) {
- confusionOthersList.innerHTML = '';
- }
- if ( timingDiv ) {
- timingDiv.classList.add( 'plugin-check-namer-hidden' );
- }
- if ( timingValue ) {
- setText( timingValue, '' );
- }
- if ( tokensDiv ) {
- tokensDiv.classList.add( 'plugin-check-namer-hidden' );
- }
- if ( tokensValue ) {
- setText( tokensValue, '' );
- }
- if ( verdictContainer ) {
- verdictContainer.classList.add( 'plugin-check-namer-hidden' );
- }
-
- // Record start time.
- const startTime = Date.now();
-
- setLoading( true );
-
- const formData = new FormData();
- formData.append( 'action', 'plugin_check_namer_analyze' );
- formData.append( 'nonce', pluginCheckNamer.nonce );
- formData.append( 'plugin_name', name );
- if ( authorInput ) {
- formData.append(
- 'author_name',
- ( authorInput.value || '' ).trim()
- );
- }
- if ( modelSelect ) {
- formData.append(
- 'model_preference',
- ( modelSelect.value || '' ).trim()
- );
- }
-
- fetch( pluginCheckNamer.ajaxUrl, {
- method: 'POST',
- credentials: 'same-origin',
- body: formData,
- } )
- .then( function ( response ) {
- return response.json();
- } )
- .then( function ( payload ) {
- if ( ! payload || ! payload.success ) {
- throw new Error(
- payload && payload.data && payload.data.message
- ? payload.data.message
- : pluginCheckNamer.messages.genericError
- );
- }
-
- setText( verdictEl, payload.data.verdict || '' );
- setHtml( explainEl, payload.data.explanation || '' );
-
- // Set border color based on verdict.
- if ( verdictContainer ) {
- const verdict = (
- payload.data.verdict || ''
- ).toLowerCase();
- let borderColor = '#2271b1'; // Default blue.
-
- if ( verdict.indexOf( 'disallowed' ) !== -1 ) {
- borderColor = '#d63638'; // Red for disallowed.
- } else if (
- verdict.indexOf( 'good' ) !== -1 ||
- verdict.indexOf( 'low' ) !== -1 ||
- verdict.indexOf( 'no issues' ) !== -1
- ) {
- borderColor = '#00a32a'; // Green for good.
- } else if (
- verdict.indexOf( 'generally allowable' ) !== -1 ||
- verdict.indexOf( 'allowable' ) !== -1
- ) {
- borderColor = '#2271b1'; // Blue for generally allowable.
- } else if (
- verdict.indexOf( 'review' ) !== -1 ||
- verdict.indexOf( 'medium' ) !== -1 ||
- verdict.indexOf( 'issues found' ) !== -1
- ) {
- borderColor = '#dba617'; // Yellow/orange for needs review.
- } else if (
- verdict.indexOf( 'problematic' ) !== -1 ||
- verdict.indexOf( 'high' ) !== -1
- ) {
- borderColor = '#d63638'; // Red for problematic.
- }
-
- verdictContainer.style.borderLeftColor = borderColor;
- verdictContainer.classList.remove(
- 'plugin-check-namer-hidden'
- );
- }
-
- // Calculate and display elapsed time.
- if ( timingDiv && timingValue ) {
- const endTime = Date.now();
- const elapsedSeconds = Math.round(
- ( endTime - startTime ) / 1000
- );
- timingValue.textContent =
- elapsedSeconds + ' ' + 'seconds';
- timingDiv.classList.remove(
- 'plugin-check-namer-hidden'
- );
- }
-
- // Display token usage if available.
- if (
- tokensDiv &&
- tokensValue &&
- payload.data.token_usage
- ) {
- const tokenUsage = payload.data.token_usage;
- let tokensText = '';
-
- // Add AI provider and model info if available.
- if ( payload.data.ai_info ) {
- const parts = [];
- if ( payload.data.ai_info.provider ) {
- parts.push( payload.data.ai_info.provider );
- }
- if ( payload.data.ai_info.model ) {
- parts.push( payload.data.ai_info.model );
- }
- if ( parts.length ) {
- tokensText = parts.join( ' / ' ) + ' - ';
- }
- }
-
- const totalTokens =
- tokenUsage.total_tokens ||
- ( tokenUsage.prompt_tokens &&
- tokenUsage.completion_tokens
- ? tokenUsage.prompt_tokens +
- tokenUsage.completion_tokens
- : null );
-
- if ( totalTokens ) {
- tokensText += totalTokens + ' total';
- }
-
- // Add breakdown with prompt and completion tokens.
- if (
- tokenUsage.prompt_tokens &&
- tokenUsage.completion_tokens
- ) {
- tokensText +=
- ' (' +
- tokenUsage.prompt_tokens +
- ' prompt + ' +
- tokenUsage.completion_tokens +
- ' completion)';
- }
-
- // Add similar name query tokens if available.
- if (
- tokenUsage.similar_name &&
- tokenUsage.similar_name.total_tokens
- ) {
- tokensText +=
- ' [Similar name query: ' +
- tokenUsage.similar_name.total_tokens +
- ' tokens]';
- }
-
- tokensValue.textContent = tokensText;
- tokensDiv.classList.remove(
- 'plugin-check-namer-hidden'
- );
- }
-
- // Display confusion_existing_plugins if available.
- if (
- confusionPluginsDiv &&
- confusionPluginsList &&
- payload.data.confusion_existing_plugins &&
- payload.data.confusion_existing_plugins.length > 0
- ) {
- confusionPluginsList.innerHTML = '';
- payload.data.confusion_existing_plugins.forEach(
- function ( plugin ) {
- const div = document.createElement( 'div' );
- div.className =
- 'plugin-check-namer-confusion-item';
- div.innerHTML =
- '' +
- escapeHtml( plugin.name || '' ) +
- '' +
- ( plugin.active_installations
- ? ' (' +
- escapeHtml(
- plugin.active_installations
- ) +
- ' ' +
- 'active installs' +
- ')'
- : '' ) +
- ( plugin.owner_username
- ? ' - ' +
- escapeHtml( plugin.owner_username ) +
- ''
- : '' ) +
- '
' +
- '' +
- escapeHtml( plugin.explanation || '' ) +
- '' +
- ( plugin.link
- ? '
' +
- escapeHtml( plugin.link ) +
- ''
- : '' );
- confusionPluginsList.appendChild( div );
- }
- );
- confusionPluginsDiv.classList.remove(
- 'plugin-check-namer-hidden'
- );
- } else if ( confusionPluginsDiv ) {
- confusionPluginsDiv.classList.add(
- 'plugin-check-namer-hidden'
- );
- }
-
- // Display confusion_existing_others if available.
- if (
- confusionOthersDiv &&
- confusionOthersList &&
- payload.data.confusion_existing_others &&
- payload.data.confusion_existing_others.length > 0
- ) {
- confusionOthersList.innerHTML = '';
- payload.data.confusion_existing_others.forEach(
- function ( item ) {
- const div = document.createElement( 'div' );
- div.className =
- 'plugin-check-namer-confusion-item plugin-check-namer-confusion-item-others';
- div.innerHTML =
- '' +
- escapeHtml( item.name || '' ) +
- '' +
- '
' +
- '' +
- escapeHtml( item.explanation || '' ) +
- '' +
- ( item.link
- ? '
' +
- escapeHtml( item.link ) +
- ''
- : '' );
- confusionOthersList.appendChild( div );
- }
- );
- confusionOthersDiv.classList.remove(
- 'plugin-check-namer-hidden'
- );
- } else if ( confusionOthersDiv ) {
- confusionOthersDiv.classList.add(
- 'plugin-check-namer-hidden'
- );
- }
-
- if ( resultWrap ) {
- resultWrap.classList.remove(
- 'plugin-check-namer-hidden'
- );
- }
- } )
- .catch( function ( err ) {
- setText(
- errorEl,
- err && err.message
- ? err.message
- : pluginCheckNamer.messages.genericError
- );
-
- if ( errorDiv ) {
- errorDiv.classList.remove(
- 'plugin-check-namer-hidden'
- );
- }
- } )
- .finally( function () {
- setLoading( false );
- } );
- } );
- } );
-} )();
diff --git a/includes/Admin/Namer_Page.php b/includes/Admin/Namer_Page.php
deleted file mode 100644
index cb4b0d415..000000000
--- a/includes/Admin/Namer_Page.php
+++ /dev/null
@@ -1,460 +0,0 @@
-hook_suffix = add_management_page(
- __( 'Plugin Check Namer', 'plugin-check' ),
- __( 'Plugin Check Namer', 'plugin-check' ),
- 'manage_options',
- self::MENU_SLUG,
- array( $this, 'render_page' )
- );
- }
-
- /**
- * Enqueues scripts for the tools page.
- *
- * @since 1.8.0
- *
- * @param string $hook_suffix Current admin page hook suffix.
- */
- public function enqueue_scripts( $hook_suffix ) {
- if ( $hook_suffix !== $this->hook_suffix ) {
- return;
- }
-
- wp_enqueue_style(
- 'plugin-check-admin',
- plugins_url( 'assets/css/plugin-check-admin.css', WP_PLUGIN_CHECK_MAIN_FILE ),
- array(),
- WP_PLUGIN_CHECK_VERSION
- );
-
- wp_enqueue_script(
- 'plugin-check-namer',
- plugins_url( 'assets/js/plugin-check-namer.js', WP_PLUGIN_CHECK_MAIN_FILE ),
- array(),
- WP_PLUGIN_CHECK_VERSION,
- true
- );
-
- wp_localize_script(
- 'plugin-check-namer',
- 'pluginCheckNamer',
- array(
- 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
- 'nonce' => wp_create_nonce( 'plugin_check_namer_ajax' ),
- 'messages' => array(
- 'missingName' => __( 'Please enter a plugin name.', 'plugin-check' ),
- 'genericError' => __( 'An unexpected error occurred.', 'plugin-check' ),
- ),
- )
- );
- }
-
- /**
- * AJAX handler to analyze a plugin name.
- *
- * @since 1.8.0
- */
- public function ajax_analyze() {
- check_ajax_referer( 'plugin_check_namer_ajax', 'nonce' );
-
- if ( ! current_user_can( 'manage_options' ) ) {
- wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'plugin-check' ) ) );
- }
-
- $name = $this->get_plugin_name_from_request();
- if ( empty( $name ) ) {
- wp_send_json_error( array( 'message' => __( 'Please enter a plugin name.', 'plugin-check' ) ) );
- }
-
- $author = $this->get_author_name_from_request();
-
- $model_preference = $this->get_model_preference_from_request();
- $ai_config = $this->get_ai_config( $model_preference );
- if ( is_wp_error( $ai_config ) ) {
- wp_send_json_error( array( 'message' => $ai_config->get_error_message() ) );
- }
-
- $analysis = $this->run_name_analysis( $ai_config['model_preference'], $name, $author );
- if ( is_wp_error( $analysis ) ) {
- wp_send_json_error( array( 'message' => $analysis->get_error_message() ) );
- }
-
- $parsed = $this->parse_analysis( $analysis );
- $response = $this->build_ajax_response( $parsed, $analysis, $ai_config );
-
- wp_send_json_success( $response );
- }
-
- /**
- * Builds AJAX response from parsed analysis.
- *
- * @since 1.8.0
- *
- * @param array $parsed Parsed analysis.
- * @param string|array $analysis Raw analysis.
- * @param array $ai_config AI configuration with model preference info.
- * @return array Response array.
- */
- protected function build_ajax_response( $parsed, $analysis, $ai_config = array() ) {
- $raw_output = $this->get_raw_output( $parsed, $analysis );
- $raw_output = $this->format_json_output( $raw_output );
-
- $response = array(
- 'verdict' => $parsed['verdict'],
- 'explanation' => $parsed['explanation'],
- 'raw' => $raw_output,
- );
-
- if ( ! empty( $parsed['confusion_existing_plugins'] ) ) {
- $response['confusion_existing_plugins'] = $parsed['confusion_existing_plugins'];
- }
- if ( ! empty( $parsed['confusion_existing_others'] ) ) {
- $response['confusion_existing_others'] = $parsed['confusion_existing_others'];
- }
- if ( ! empty( $parsed['token_usage'] ) ) {
- $response['token_usage'] = $parsed['token_usage'];
- }
-
- // Add AI model preference information.
- if ( ! empty( $ai_config['model_preference'] ) ) {
- $response['ai_info'] = array(
- 'model' => $ai_config['model_preference'],
- );
- }
-
- return $response;
- }
-
- /**
- * Gets plugin name from request.
- *
- * @since 1.8.0
- *
- * @return string Plugin name or empty string.
- */
- protected function get_plugin_name_from_request() {
- $name = isset( $_POST['plugin_name'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin_name'] ) ) : '';
- return trim( $name );
- }
-
- /**
- * Gets author name from request.
- *
- * @since 1.8.0
- *
- * @return string Author name or empty string.
- */
- protected function get_author_name_from_request() {
- $author = isset( $_POST['author_name'] ) ? sanitize_text_field( wp_unslash( $_POST['author_name'] ) ) : '';
- return trim( $author );
- }
-
- /**
- * Renders the page.
- *
- * @since 1.8.0
- *
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
- */
- public function render_page() {
- if ( ! current_user_can( 'manage_options' ) ) {
- return;
- }
-
- $ai_config = $this->get_ai_config();
- ?>
-
-
-
-
-
-
- get_error_message() );
- ?>
-
-
-
- check_ai_prerequisites() && is_wp_error( $this->check_ai_connectors() ) ) {
- ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- get_model_preference_from_request();
- $user_id = get_current_user_id();
-
- if ( empty( $input ) ) {
- $this->handle_analyze_error( $user_id, '', new WP_Error( 'missing_input', __( 'Please enter a plugin name.', 'plugin-check' ) ) );
- return;
- }
-
- $ai_config = $this->get_ai_config( $model_preference );
- if ( is_wp_error( $ai_config ) ) {
- $this->handle_analyze_error( $user_id, $input, $ai_config );
- return;
- }
-
- $analysis = $this->run_name_analysis( $ai_config['model_preference'], $input, $author );
-
- if ( is_wp_error( $analysis ) ) {
- $this->handle_analyze_error( $user_id, $input, $analysis );
- return;
- }
-
- $this->store_result(
- $user_id,
- array(
- 'input' => $input,
- 'analysis' => $analysis,
- )
- );
- wp_safe_redirect( $this->get_page_url() );
- exit;
- }
-
- /**
- * Handles analyze error and redirects.
- *
- * @since 1.8.0
- *
- * @param int $user_id User ID.
- * @param string $input Input value.
- * @param WP_Error $error Error object.
- */
- protected function handle_analyze_error( $user_id, $input, $error ) {
- $this->store_result(
- $user_id,
- array(
- 'input' => $input,
- 'error' => $error,
- )
- );
- wp_safe_redirect( $this->get_page_url() );
- exit;
- }
-
- /**
- * Gets the page URL.
- *
- * @since 1.8.0
- *
- * @return string
- */
- protected function get_page_url() {
- return add_query_arg( array( 'page' => self::MENU_SLUG ), admin_url( 'tools.php' ) );
- }
-}
diff --git a/includes/Checker/Abstract_Check_Runner.php b/includes/Checker/Abstract_Check_Runner.php
index 8f66010ac..5040bec8b 100644
--- a/includes/Checker/Abstract_Check_Runner.php
+++ b/includes/Checker/Abstract_Check_Runner.php
@@ -342,11 +342,22 @@ final public function set_ai_model_preference( $model_preference ) {
*
* @return bool True if AI analysis should be used, false otherwise.
*/
- protected function should_use_ai() {
+ public function should_use_ai() {
// Check if explicitly set via setter (e.g., CLI flag or checkbox).
return $this->use_ai;
}
+ /**
+ * Gets the AI model preference for analysis.
+ *
+ * @since 2.1.0
+ *
+ * @return string Model preference.
+ */
+ final public function get_ai_model_preference() {
+ return $this->ai_model_preference;
+ }
+
/**
* Sets categories for filtering the checks.
*
diff --git a/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php b/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php
new file mode 100644
index 000000000..5e5a4d109
--- /dev/null
+++ b/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php
@@ -0,0 +1,179 @@
+should_use_ai() ) {
+ return;
+ }
+
+ if ( is_wp_error( $this->check_ai_prerequisites() ) || is_wp_error( $this->check_ai_connectors() ) ) {
+ return;
+ }
+
+ // Retrieve model preference.
+ $model_preference = '';
+ if ( method_exists( $runner, 'get_ai_model_preference' ) ) {
+ $model_preference = $runner->get_ai_model_preference();
+ }
+ if ( empty( $model_preference ) && class_exists( Settings_Page::class ) ) {
+ $model_preference = Settings_Page::get_model_preference();
+ }
+
+ if ( ! function_exists( 'get_plugin_data' ) ) {
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
+ }
+
+ $plugin_main_file = $result->plugin()->main_file();
+ $plugin_header = get_plugin_data( $plugin_main_file );
+ $plugin_name = isset( $plugin_header['Name'] ) ? $plugin_header['Name'] : '';
+ $author_name = isset( $plugin_header['AuthorName'] ) ? $plugin_header['AuthorName'] : '';
+
+ if ( empty( $plugin_name ) ) {
+ return;
+ }
+
+ $analysis = $this->run_name_analysis( $model_preference, $plugin_name, $author_name );
+ if ( is_wp_error( $analysis ) ) {
+ $this->add_result_warning_for_file(
+ $result,
+ sprintf(
+ /* translators: %s: Error message. */
+ __( 'AI plugin name check failed: %s', 'plugin-check' ),
+ $analysis->get_error_message()
+ ),
+ 'ai_name_check_failed',
+ $plugin_main_file
+ );
+ return;
+ }
+
+ $parsed = $this->parse_analysis( $analysis );
+
+ // If there is a verdict indicating issues:
+ if ( isset( $parsed['processed_data'] ) ) {
+ $data = $parsed['processed_data'];
+
+ // 1. If disallowed:
+ if ( ! empty( $data['disallowed'] ) ) {
+ $msg = isset( $data['disallowed_explanation'] ) ? $data['disallowed_explanation'] : __( 'The plugin name is disallowed.', 'plugin-check' );
+ $this->add_result_error_for_file(
+ $result,
+ $msg,
+ 'plugin_name_disallowed',
+ $plugin_main_file
+ );
+ }
+
+ // 2. If possible naming issues:
+ if ( ! empty( $data['possible_naming_issues'] ) ) {
+ $msg = isset( $data['naming_explanation'] ) ? $data['naming_explanation'] : __( 'The plugin name has possible naming issues.', 'plugin-check' );
+ $this->add_result_warning_for_file(
+ $result,
+ $msg,
+ 'plugin_name_issue',
+ $plugin_main_file
+ );
+ }
+
+ // 3. If possible owner/trademark issues:
+ if ( ! empty( $data['possible_owner_issues'] ) ) {
+ $msg = isset( $data['owner_explanation'] ) ? $data['owner_explanation'] : __( 'The plugin name has possible trademark or ownership issues.', 'plugin-check' );
+ $this->add_result_warning_for_file(
+ $result,
+ $msg,
+ 'plugin_name_trademark_issue',
+ $plugin_main_file
+ );
+ }
+
+ // 4. Group list of similar plugins/trademarks into recommendations:
+ if ( ! empty( $parsed['confusion_existing_plugins'] ) && is_array( $parsed['confusion_existing_plugins'] ) ) {
+ $plugins_list = array();
+ foreach ( $parsed['confusion_existing_plugins'] as $plugin ) {
+ $plugins_list[] = sprintf( '%s (%s, %s active installs)', $plugin['name'], $plugin['similarity_level'], $plugin['active_installations'] );
+ }
+ $this->add_result_warning_for_file(
+ $result,
+ sprintf(
+ /* translators: %s: List of similar plugins. */
+ __( 'Plugin name is similar to existing plugins: %s', 'plugin-check' ),
+ implode( '; ', $plugins_list )
+ ),
+ 'plugin_name_similarity',
+ $plugin_main_file
+ );
+ }
+ }
+ }
+
+ /**
+ * Gets the description for the check.
+ *
+ * @since 2.1.0
+ *
+ * @return string Description.
+ */
+ public function get_description(): string {
+ return __( 'Checks the plugin name for guideline compliance, trademark conflicts, and similarity with existing plugins using AI.', 'plugin-check' );
+ }
+
+ /**
+ * Gets the documentation URL for the check.
+ *
+ * @since 2.1.0
+ *
+ * @return string The documentation URL.
+ */
+ public function get_documentation_url(): string {
+ return __( 'https://developer.wordpress.org/plugins/wordpress-org/detailed-plugin-guidelines/', 'plugin-check' );
+ }
+}
diff --git a/includes/Checker/Default_Check_Repository.php b/includes/Checker/Default_Check_Repository.php
index d4d5c548d..00778a080 100644
--- a/includes/Checker/Default_Check_Repository.php
+++ b/includes/Checker/Default_Check_Repository.php
@@ -105,6 +105,7 @@ private function register_default_checks() {
'external_admin_menu_links' => new Checks\Plugin_Repo\External_Admin_Menu_Links_Check(),
'wp_functions_compatibility' => new Checks\Plugin_Repo\WP_Functions_Compatibility_Check(),
'ai_provider' => new Checks\General\AI_Provider_Check(),
+ 'ai_name' => new Checks\Plugin_Repo\AI_Name_Check(),
)
);
diff --git a/includes/Plugin_Main.php b/includes/Plugin_Main.php
index 210034611..cfe422fc3 100644
--- a/includes/Plugin_Main.php
+++ b/includes/Plugin_Main.php
@@ -78,14 +78,12 @@ public function add_hooks() {
// Create the Settings page.
$settings_page = new Settings_Page();
$settings_page->add_hooks();
-
- // Create the Plugin Check Namer tool page.
- $namer_page_class = '\\WordPress\\Plugin_Check\\Admin\\Namer_Page';
- $namer_page = new $namer_page_class();
- $namer_page->add_hooks();
+<<<<<<< HEAD
// Create the SVN Checker page.
$svn_checker_page = new SVN_Checker_Page();
$svn_checker_page->add_hooks();
+=======
+>>>>>>> 443d8a22 (feat: integrate Plugin Check Namer as standard AI Name Check static check)
}
}
diff --git a/tests/phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php b/tests/phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php
new file mode 100644
index 000000000..3bc47a50c
--- /dev/null
+++ b/tests/phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php
@@ -0,0 +1,33 @@
+createMock( AJAX_Runner::class );
+ $runner->method( 'should_use_ai' )->willReturn( false );
+
+ $reflection = new \ReflectionClass( Plugin_Request_Utility::class );
+ $property = $reflection->getProperty( 'runner' );
+ $property->setAccessible( true );
+ $property->setValue( null, $runner );
+
+ $check = new AI_Name_Check();
+ $context = $this->createMock( Check_Context::class );
+ $result = new Check_Result( $context );
+
+ $check->run( $result );
+
+ $this->assertEmpty( $result->get_errors() );
+ $this->assertEmpty( $result->get_warnings() );
+
+ $property->setValue( null, null );
+ }
+}
From e002a1c5ebde3f23c911c609cfa9527e08a90720 Mon Sep 17 00:00:00 2001
From: Ishita Jagtap <143935560+ishitaj34@users.noreply.github.com>
Date: Mon, 13 Jul 2026 12:24:36 +0530
Subject: [PATCH 02/11] fix: resolve PHPCS formatting
---
includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php | 8 ++++----
.../phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php | 4 ++--
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php b/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php
index 5e5a4d109..aba4a0d27 100644
--- a/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php
+++ b/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php
@@ -7,15 +7,15 @@
namespace WordPress\Plugin_Check\Checker\Checks\Plugin_Repo;
+use WordPress\Plugin_Check\Admin\Settings_Page;
use WordPress\Plugin_Check\Checker\Check_Categories;
use WordPress\Plugin_Check\Checker\Check_Result;
use WordPress\Plugin_Check\Checker\Static_Check;
-use WordPress\Plugin_Check\Traits\Amend_Check_Result;
-use WordPress\Plugin_Check\Traits\Stable_Check;
use WordPress\Plugin_Check\Traits\AI_Check_Names;
use WordPress\Plugin_Check\Traits\AI_Utils;
+use WordPress\Plugin_Check\Traits\Amend_Check_Result;
+use WordPress\Plugin_Check\Traits\Stable_Check;
use WordPress\Plugin_Check\Utilities\Plugin_Request_Utility;
-use WordPress\Plugin_Check\Admin\Settings_Page;
/**
* Check for plugin name issues using AI.
@@ -98,7 +98,7 @@ public function run( Check_Result $result ) {
$parsed = $this->parse_analysis( $analysis );
- // If there is a verdict indicating issues:
+ // If there is a verdict indicating issues.
if ( isset( $parsed['processed_data'] ) ) {
$data = $parsed['processed_data'];
diff --git a/tests/phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php b/tests/phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php
index 3bc47a50c..54864aa55 100644
--- a/tests/phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php
+++ b/tests/phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php
@@ -1,12 +1,12 @@
-
Date: Mon, 13 Jul 2026 12:30:39 +0530
Subject: [PATCH 03/11] fix: resolve sorting
---
.../phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/tests/phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php b/tests/phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php
index 54864aa55..759cd6d1f 100644
--- a/tests/phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php
+++ b/tests/phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php
@@ -1,11 +1,10 @@
- setValue( null, $runner );
$check = new AI_Name_Check();
- $context = $this->createMock( Check_Context::class );
+ $context = new Check_Context( WP_PLUGIN_CHECK_MAIN_FILE );
$result = new Check_Result( $context );
$check->run( $result );
From 589006b82c6e2ae54f934b4b5fb0e4bc96a681be Mon Sep 17 00:00:00 2001
From: Ishita Jagtap <143935560+ishitaj34@users.noreply.github.com>
Date: Mon, 13 Jul 2026 12:37:41 +0530
Subject: [PATCH 04/11] refactor: reduce method length
---
.../Checks/Plugin_Repo/AI_Name_Check.php | 143 ++++++++++++------
1 file changed, 93 insertions(+), 50 deletions(-)
diff --git a/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php b/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php
index aba4a0d27..30c5bde6c 100644
--- a/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php
+++ b/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php
@@ -97,61 +97,104 @@ public function run( Check_Result $result ) {
}
$parsed = $this->parse_analysis( $analysis );
+ $this->process_analysis_results( $result, $parsed, $plugin_main_file );
+ }
- // If there is a verdict indicating issues.
- if ( isset( $parsed['processed_data'] ) ) {
- $data = $parsed['processed_data'];
-
- // 1. If disallowed:
- if ( ! empty( $data['disallowed'] ) ) {
- $msg = isset( $data['disallowed_explanation'] ) ? $data['disallowed_explanation'] : __( 'The plugin name is disallowed.', 'plugin-check' );
- $this->add_result_error_for_file(
- $result,
- $msg,
- 'plugin_name_disallowed',
- $plugin_main_file
- );
- }
+ /**
+ * Process analysis results and add appropriate warnings/errors.
+ *
+ * @since 2.1.0
+ *
+ * @param Check_Result $result The check result.
+ * @param array $parsed The parsed analysis data.
+ * @param string $plugin_main_file The plugin main file path.
+ */
+ private function process_analysis_results( Check_Result $result, array $parsed, string $plugin_main_file ) {
+ if ( ! isset( $parsed['processed_data'] ) ) {
+ return;
+ }
- // 2. If possible naming issues:
- if ( ! empty( $data['possible_naming_issues'] ) ) {
- $msg = isset( $data['naming_explanation'] ) ? $data['naming_explanation'] : __( 'The plugin name has possible naming issues.', 'plugin-check' );
- $this->add_result_warning_for_file(
- $result,
- $msg,
- 'plugin_name_issue',
- $plugin_main_file
- );
- }
+ $data = $parsed['processed_data'];
- // 3. If possible owner/trademark issues:
- if ( ! empty( $data['possible_owner_issues'] ) ) {
- $msg = isset( $data['owner_explanation'] ) ? $data['owner_explanation'] : __( 'The plugin name has possible trademark or ownership issues.', 'plugin-check' );
- $this->add_result_warning_for_file(
- $result,
- $msg,
- 'plugin_name_trademark_issue',
- $plugin_main_file
- );
- }
+ $this->check_disallowed_name( $result, $data, $plugin_main_file );
+ $this->check_naming_issues( $result, $data, $plugin_main_file );
+ $this->check_owner_issues( $result, $data, $plugin_main_file );
+ $this->check_similar_plugins( $result, $parsed, $plugin_main_file );
+ }
- // 4. Group list of similar plugins/trademarks into recommendations:
- if ( ! empty( $parsed['confusion_existing_plugins'] ) && is_array( $parsed['confusion_existing_plugins'] ) ) {
- $plugins_list = array();
- foreach ( $parsed['confusion_existing_plugins'] as $plugin ) {
- $plugins_list[] = sprintf( '%s (%s, %s active installs)', $plugin['name'], $plugin['similarity_level'], $plugin['active_installations'] );
- }
- $this->add_result_warning_for_file(
- $result,
- sprintf(
- /* translators: %s: List of similar plugins. */
- __( 'Plugin name is similar to existing plugins: %s', 'plugin-check' ),
- implode( '; ', $plugins_list )
- ),
- 'plugin_name_similarity',
- $plugin_main_file
- );
+ /**
+ * Check if the plugin name is disallowed.
+ *
+ * @since 2.1.0
+ *
+ * @param Check_Result $result The check result.
+ * @param array $data The parsed analysis data.
+ * @param string $plugin_main_file The plugin main file path.
+ */
+ private function check_disallowed_name( Check_Result $result, array $data, string $plugin_main_file ) {
+ if ( ! empty( $data['disallowed'] ) ) {
+ $msg = isset( $data['disallowed_explanation'] ) ? $data['disallowed_explanation'] : __( 'The plugin name is disallowed.', 'plugin-check' );
+ $this->add_result_error_for_file( $result, $msg, 'plugin_name_disallowed', $plugin_main_file );
+ }
+ }
+
+ /**
+ * Check for possible naming issues.
+ *
+ * @since 2.1.0
+ *
+ * @param Check_Result $result The check result.
+ * @param array $data The parsed analysis data.
+ * @param string $plugin_main_file The plugin main file path.
+ */
+ private function check_naming_issues( Check_Result $result, array $data, string $plugin_main_file ) {
+ if ( ! empty( $data['possible_naming_issues'] ) ) {
+ $msg = isset( $data['naming_explanation'] ) ? $data['naming_explanation'] : __( 'The plugin name has possible naming issues.', 'plugin-check' );
+ $this->add_result_warning_for_file( $result, $msg, 'plugin_name_issue', $plugin_main_file );
+ }
+ }
+
+ /**
+ * Check for possible owner issues.
+ *
+ * @since 2.1.0
+ *
+ * @param Check_Result $result The check result.
+ * @param array $data The parsed analysis data.
+ * @param string $plugin_main_file The plugin main file path.
+ */
+ private function check_owner_issues( Check_Result $result, array $data, string $plugin_main_file ) {
+ if ( ! empty( $data['possible_owner_issues'] ) ) {
+ $msg = isset( $data['owner_explanation'] ) ? $data['owner_explanation'] : __( 'The plugin name has possible trademark or ownership issues.', 'plugin-check' );
+ $this->add_result_warning_for_file( $result, $msg, 'plugin_name_trademark_issue', $plugin_main_file );
+ }
+ }
+
+ /**
+ * Check for similar plugins.
+ *
+ * @since 2.1.0
+ *
+ * @param Check_Result $result The check result.
+ * @param array $parsed The parsed analysis data.
+ * @param string $plugin_main_file The plugin main file path.
+ */
+ private function check_similar_plugins( Check_Result $result, array $parsed, string $plugin_main_file ) {
+ if ( ! empty( $parsed['confusion_existing_plugins'] ) && is_array( $parsed['confusion_existing_plugins'] ) ) {
+ $plugins_list = array();
+ foreach ( $parsed['confusion_existing_plugins'] as $plugin ) {
+ $plugins_list[] = sprintf( '%s (%s, %s active installs)', $plugin['name'], $plugin['similarity_level'], $plugin['active_installations'] );
}
+ $this->add_result_warning_for_file(
+ $result,
+ sprintf(
+ /* translators: %s: List of similar plugins. */
+ __( 'Plugin name is similar to existing plugins: %s', 'plugin-check' ),
+ implode( '; ', $plugins_list )
+ ),
+ 'plugin_name_similarity',
+ $plugin_main_file
+ );
}
}
From a3d94069690d11a649e7ed83776242f28fde2bf5 Mon Sep 17 00:00:00 2001
From: Ishita Jagtap <143935560+ishitaj34@users.noreply.github.com>
Date: Mon, 13 Jul 2026 12:41:50 +0530
Subject: [PATCH 05/11] refactor: reduce run method NPath complexity to satisfy
PHPMD
---
.../Checks/Plugin_Repo/AI_Name_Check.php | 100 +++++++++++++++---
1 file changed, 83 insertions(+), 17 deletions(-)
diff --git a/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php b/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php
index 30c5bde6c..384fe68cf 100644
--- a/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php
+++ b/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php
@@ -49,39 +49,105 @@ public function get_categories() {
*/
public function run( Check_Result $result ) {
$runner = Plugin_Request_Utility::get_runner();
-
- // Only run when AI analysis is enabled.
- if ( ! $runner || ! method_exists( $runner, 'should_use_ai' ) || ! $runner->should_use_ai() ) {
+ if ( ! $this->should_run_ai_check( $runner ) ) {
return;
}
- if ( is_wp_error( $this->check_ai_prerequisites() ) || is_wp_error( $this->check_ai_connectors() ) ) {
+ $model_preference = $this->get_model_preference( $runner );
+ $plugin_data = $this->get_plugin_name_and_author( $result );
+ if ( ! $plugin_data ) {
return;
}
- // Retrieve model preference.
- $model_preference = '';
- if ( method_exists( $runner, 'get_ai_model_preference' ) ) {
- $model_preference = $runner->get_ai_model_preference();
+ $analysis = $this->run_name_analysis( $model_preference, $plugin_data['name'], $plugin_data['author'] );
+ $this->handle_analysis_response( $result, $analysis, $plugin_data['file'] );
+ }
+
+ /**
+ * Determines if the AI check should run.
+ *
+ * @since 2.1.0
+ *
+ * @param mixed $runner The active runner.
+ * @return bool True if the check should run, false otherwise.
+ */
+ private function should_run_ai_check( $runner ) {
+ if ( ! $runner ) {
+ return false;
+ }
+ if ( ! method_exists( $runner, 'should_use_ai' ) ) {
+ return false;
+ }
+ if ( ! $runner->should_use_ai() ) {
+ return false;
+ }
+ if ( is_wp_error( $this->check_ai_prerequisites() ) ) {
+ return false;
+ }
+ if ( is_wp_error( $this->check_ai_connectors() ) ) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Gets the selected AI model preference.
+ *
+ * @since 2.1.0
+ *
+ * @param mixed $runner The active runner.
+ * @return string The model preference.
+ */
+ private function get_model_preference( $runner ) {
+ $model = '';
+ if ( $runner && method_exists( $runner, 'get_ai_model_preference' ) ) {
+ $model = $runner->get_ai_model_preference();
}
- if ( empty( $model_preference ) && class_exists( Settings_Page::class ) ) {
- $model_preference = Settings_Page::get_model_preference();
+ if ( empty( $model ) && class_exists( Settings_Page::class ) ) {
+ $model = Settings_Page::get_model_preference();
}
+ return $model;
+ }
+ /**
+ * Gets the plugin name and author from headers.
+ *
+ * @since 2.1.0
+ *
+ * @param Check_Result $result The check result.
+ * @return array|null Name and author if found, null otherwise.
+ */
+ private function get_plugin_name_and_author( Check_Result $result ) {
if ( ! function_exists( 'get_plugin_data' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
- $plugin_main_file = $result->plugin()->main_file();
- $plugin_header = get_plugin_data( $plugin_main_file );
- $plugin_name = isset( $plugin_header['Name'] ) ? $plugin_header['Name'] : '';
- $author_name = isset( $plugin_header['AuthorName'] ) ? $plugin_header['AuthorName'] : '';
+ $file = $result->plugin()->main_file();
+ $header = get_plugin_data( $file );
+ $name = isset( $header['Name'] ) ? $header['Name'] : '';
+ $author = isset( $header['AuthorName'] ) ? $header['AuthorName'] : '';
- if ( empty( $plugin_name ) ) {
- return;
+ if ( empty( $name ) ) {
+ return null;
}
- $analysis = $this->run_name_analysis( $model_preference, $plugin_name, $author_name );
+ return array(
+ 'name' => $name,
+ 'author' => $author,
+ 'file' => $file,
+ );
+ }
+
+ /**
+ * Handles the AI analysis response and parses results.
+ *
+ * @since 2.1.0
+ *
+ * @param Check_Result $result The check result.
+ * @param mixed $analysis The analysis response or WP_Error.
+ * @param string $plugin_main_file The plugin main file path.
+ */
+ private function handle_analysis_response( Check_Result $result, $analysis, string $plugin_main_file ) {
if ( is_wp_error( $analysis ) ) {
$this->add_result_warning_for_file(
$result,
From 462fabf8256571f5d23e7d346506bc070ea18ebb Mon Sep 17 00:00:00 2001
From: Ishita Jagtap <143935560+ishitaj34@users.noreply.github.com>
Date: Wed, 15 Jul 2026 20:57:51 +0530
Subject: [PATCH 06/11] refactor: separate AI name check option
---
assets/js/plugin-check-admin.js | 19 +++++++++++
includes/Admin/Admin_AJAX.php | 15 +++++----
includes/CLI/Plugin_Check_Command.php | 5 +++
includes/Checker/Abstract_Check_Runner.php | 32 ++++++++++++++++++-
.../Checks/Plugin_Repo/AI_Name_Check.php | 32 +++++++++----------
templates/admin-page.php | 3 ++
.../Checker/Checks/AI_Name_Check_Tests.php | 2 +-
7 files changed, 84 insertions(+), 24 deletions(-)
diff --git a/assets/js/plugin-check-admin.js b/assets/js/plugin-check-admin.js
index 9b17043b3..d0578264f 100644
--- a/assets/js/plugin-check-admin.js
+++ b/assets/js/plugin-check-admin.js
@@ -38,6 +38,7 @@
'plugin-check__include-experimental'
);
const useAi = document.getElementById( 'plugin-check__use-ai' );
+ const useAiName = document.getElementById( 'plugin-check__use-ai-name' );
// Handle disabling the Check it button when a plugin is not selected.
function canRunChecks() {
@@ -140,6 +141,9 @@
if ( useAi ) {
useAi.disabled = true;
}
+ if ( useAiName ) {
+ useAiName.disabled = true;
+ }
if ( includeExperimental ) {
includeExperimental.disabled = true;
}
@@ -220,6 +224,9 @@
if ( useAi ) {
useAi.disabled = false;
}
+ if ( useAiName ) {
+ useAiName.disabled = false;
+ }
if ( includeExperimental ) {
includeExperimental.disabled = false;
}
@@ -654,6 +661,10 @@
includeExperimentalInput
);
pluginCheckData.append( 'use-ai', useAiInput );
+ pluginCheckData.append(
+ 'use-ai-name',
+ useAiName && useAiName.checked ? 1 : 0
+ );
for ( let i = 0; i < checks.length; i++ ) {
pluginCheckData.append( 'checks[]', checks[ i ] );
@@ -718,6 +729,10 @@
includeExperimentalInput
);
pluginCheckData.append( 'use-ai', useAiInput );
+ pluginCheckData.append(
+ 'use-ai-name',
+ useAiName && useAiName.checked ? 1 : 0
+ );
for ( let i = 0; i < categories.length; i++ ) {
pluginCheckData.append( 'categories[]', categories[ i ] );
@@ -1010,6 +1025,10 @@
includeExperimentalInput
);
pluginCheckData.append( 'use-ai', useAiInput );
+ pluginCheckData.append(
+ 'use-ai-name',
+ useAiName && useAiName.checked ? 1 : 0
+ );
for ( let i = 0; i < types.length; i++ ) {
pluginCheckData.append( 'types[]', types[ i ] );
diff --git a/includes/Admin/Admin_AJAX.php b/includes/Admin/Admin_AJAX.php
index 3d2b42216..7a2a15979 100644
--- a/includes/Admin/Admin_AJAX.php
+++ b/includes/Admin/Admin_AJAX.php
@@ -218,12 +218,13 @@ public function clean_up_environment() {
public function get_checks_to_run() {
$this->check_request_validity();
- $categories = filter_input( INPUT_POST, 'categories', FILTER_DEFAULT, FILTER_FORCE_ARRAY );
- $categories = is_null( $categories ) ? array() : $categories;
- $checks = filter_input( INPUT_POST, 'checks', FILTER_DEFAULT, FILTER_FORCE_ARRAY );
- $checks = is_null( $checks ) ? array() : $checks;
- $use_ai = 1 === filter_input( INPUT_POST, 'use-ai', FILTER_VALIDATE_INT );
- $runner = $this->get_ajax_runner();
+ $categories = filter_input( INPUT_POST, 'categories', FILTER_DEFAULT, FILTER_FORCE_ARRAY );
+ $categories = is_null( $categories ) ? array() : $categories;
+ $checks = filter_input( INPUT_POST, 'checks', FILTER_DEFAULT, FILTER_FORCE_ARRAY );
+ $checks = is_null( $checks ) ? array() : $checks;
+ $use_ai = 1 === filter_input( INPUT_POST, 'use-ai', FILTER_VALIDATE_INT );
+ $use_ai_name = 1 === filter_input( INPUT_POST, 'use-ai-name', FILTER_VALIDATE_INT );
+ $runner = $this->get_ajax_runner();
if ( is_wp_error( $runner ) ) {
wp_send_json_error( $runner, 500 );
@@ -285,6 +286,7 @@ public function run_checks() {
$include_experimental = 1 === filter_input( INPUT_POST, 'include-experimental', FILTER_VALIDATE_INT );
$use_ai = 1 === filter_input( INPUT_POST, 'use-ai', FILTER_VALIDATE_INT );
+ $use_ai_name = 1 === filter_input( INPUT_POST, 'use-ai-name', FILTER_VALIDATE_INT );
$types = filter_input( INPUT_POST, 'types', FILTER_DEFAULT, FILTER_FORCE_ARRAY );
$types = is_null( $types ) ? array( 'error', 'warning' ) : $types;
@@ -293,6 +295,7 @@ public function run_checks() {
$runner->set_check_slugs( $checks );
$runner->set_plugin( $plugin );
$runner->set_use_ai( $use_ai );
+ $runner->set_use_ai_name( $use_ai_name );
$results = $runner->run();
} catch ( Exception $error ) {
wp_send_json_error(
diff --git a/includes/CLI/Plugin_Check_Command.php b/includes/CLI/Plugin_Check_Command.php
index fd10abbef..a792e0bb7 100644
--- a/includes/CLI/Plugin_Check_Command.php
+++ b/includes/CLI/Plugin_Check_Command.php
@@ -157,6 +157,9 @@ public function __construct( Plugin_Context $plugin_context ) {
* [--ai-model=]
* : AI model preference for analysis (e.g., 'openai::gpt-4o'). Requires --ai.
*
+ * [--ai-name]
+ * : Enable AI-based plugin name checking.
+ *
* ## EXAMPLES
*
* wp plugin check akismet
@@ -201,6 +204,7 @@ public function check( $args, $assoc_args ) {
'mode' => 'new',
'ai' => false,
'ai-model' => '',
+ 'ai-name' => false,
)
);
@@ -262,6 +266,7 @@ static function ( $dirs ) use ( $excluded_files ) {
$runner->set_slug( $options['slug'] );
$runner->set_mode( $options['mode'] );
$runner->set_use_ai( $options['ai'] );
+ $runner->set_use_ai_name( $options['ai-name'] );
if ( ! empty( $options['ai-model'] ) ) {
$runner->set_ai_model_preference( $options['ai-model'] );
}
diff --git a/includes/Checker/Abstract_Check_Runner.php b/includes/Checker/Abstract_Check_Runner.php
index 5040bec8b..510e628b0 100644
--- a/includes/Checker/Abstract_Check_Runner.php
+++ b/includes/Checker/Abstract_Check_Runner.php
@@ -42,6 +42,14 @@ abstract class Abstract_Check_Runner implements Check_Runner {
*/
protected $use_ai = false;
+ /**
+ * Whether AI plugin name checking is enabled.
+ *
+ * @since x.x.x
+ * @var bool
+ */
+ protected $use_ai_name = false;
+
/**
* AI model preference for analysis.
*
@@ -324,6 +332,17 @@ final public function set_use_ai( $use_ai ) {
$this->use_ai = (bool) $use_ai;
}
+ /**
+ * Sets whether to use AI plugin name check.
+ *
+ * @since x.x.x
+ *
+ * @param bool $use_ai_name True to enable AI name check, false to disable.
+ */
+ final public function set_use_ai_name( $use_ai_name ) {
+ $this->use_ai_name = (bool) $use_ai_name;
+ }
+
/**
* Sets the AI model preference for analysis.
*
@@ -347,10 +366,21 @@ public function should_use_ai() {
return $this->use_ai;
}
+ /**
+ * Determines if AI plugin name check should be used.
+ *
+ * @since x.x.x
+ *
+ * @return bool True if AI name check should be used, false otherwise.
+ */
+ public function should_use_ai_name() {
+ return $this->use_ai_name;
+ }
+
/**
* Gets the AI model preference for analysis.
*
- * @since 2.1.0
+ * @since x.x.x
*
* @return string Model preference.
*/
diff --git a/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php b/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php
index 384fe68cf..10aa17eab 100644
--- a/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php
+++ b/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php
@@ -20,7 +20,7 @@
/**
* Check for plugin name issues using AI.
*
- * @since 2.1.0
+ * @since x.x.x
*/
class AI_Name_Check implements Static_Check {
@@ -32,7 +32,7 @@ class AI_Name_Check implements Static_Check {
/**
* Gets the categories for the check.
*
- * @since 2.1.0
+ * @since x.x.x
*
* @return array The categories for the check.
*/
@@ -43,7 +43,7 @@ public function get_categories() {
/**
* Runs the AI Name Check.
*
- * @since 2.1.0
+ * @since x.x.x
*
* @param Check_Result $result The check result to amend.
*/
@@ -66,7 +66,7 @@ public function run( Check_Result $result ) {
/**
* Determines if the AI check should run.
*
- * @since 2.1.0
+ * @since x.x.x
*
* @param mixed $runner The active runner.
* @return bool True if the check should run, false otherwise.
@@ -75,10 +75,10 @@ private function should_run_ai_check( $runner ) {
if ( ! $runner ) {
return false;
}
- if ( ! method_exists( $runner, 'should_use_ai' ) ) {
+ if ( ! method_exists( $runner, 'should_use_ai_name' ) ) {
return false;
}
- if ( ! $runner->should_use_ai() ) {
+ if ( ! $runner->should_use_ai_name() ) {
return false;
}
if ( is_wp_error( $this->check_ai_prerequisites() ) ) {
@@ -93,7 +93,7 @@ private function should_run_ai_check( $runner ) {
/**
* Gets the selected AI model preference.
*
- * @since 2.1.0
+ * @since x.x.x
*
* @param mixed $runner The active runner.
* @return string The model preference.
@@ -112,7 +112,7 @@ private function get_model_preference( $runner ) {
/**
* Gets the plugin name and author from headers.
*
- * @since 2.1.0
+ * @since x.x.x
*
* @param Check_Result $result The check result.
* @return array|null Name and author if found, null otherwise.
@@ -141,7 +141,7 @@ private function get_plugin_name_and_author( Check_Result $result ) {
/**
* Handles the AI analysis response and parses results.
*
- * @since 2.1.0
+ * @since x.x.x
*
* @param Check_Result $result The check result.
* @param mixed $analysis The analysis response or WP_Error.
@@ -169,7 +169,7 @@ private function handle_analysis_response( Check_Result $result, $analysis, stri
/**
* Process analysis results and add appropriate warnings/errors.
*
- * @since 2.1.0
+ * @since x.x.x
*
* @param Check_Result $result The check result.
* @param array $parsed The parsed analysis data.
@@ -191,7 +191,7 @@ private function process_analysis_results( Check_Result $result, array $parsed,
/**
* Check if the plugin name is disallowed.
*
- * @since 2.1.0
+ * @since x.x.x
*
* @param Check_Result $result The check result.
* @param array $data The parsed analysis data.
@@ -207,7 +207,7 @@ private function check_disallowed_name( Check_Result $result, array $data, strin
/**
* Check for possible naming issues.
*
- * @since 2.1.0
+ * @since x.x.x
*
* @param Check_Result $result The check result.
* @param array $data The parsed analysis data.
@@ -223,7 +223,7 @@ private function check_naming_issues( Check_Result $result, array $data, string
/**
* Check for possible owner issues.
*
- * @since 2.1.0
+ * @since x.x.x
*
* @param Check_Result $result The check result.
* @param array $data The parsed analysis data.
@@ -239,7 +239,7 @@ private function check_owner_issues( Check_Result $result, array $data, string $
/**
* Check for similar plugins.
*
- * @since 2.1.0
+ * @since x.x.x
*
* @param Check_Result $result The check result.
* @param array $parsed The parsed analysis data.
@@ -267,7 +267,7 @@ private function check_similar_plugins( Check_Result $result, array $parsed, str
/**
* Gets the description for the check.
*
- * @since 2.1.0
+ * @since x.x.x
*
* @return string Description.
*/
@@ -278,7 +278,7 @@ public function get_description(): string {
/**
* Gets the documentation URL for the check.
*
- * @since 2.1.0
+ * @since x.x.x
*
* @return string The documentation URL.
*/
diff --git a/templates/admin-page.php b/templates/admin-page.php
index d8bfe935e..26183e659 100644
--- a/templates/admin-page.php
+++ b/templates/admin-page.php
@@ -85,6 +85,9 @@
+
+
+
diff --git a/tests/phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php b/tests/phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php
index 759cd6d1f..29be2abc0 100644
--- a/tests/phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php
+++ b/tests/phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php
@@ -11,7 +11,7 @@ class AI_Name_Check_Tests extends \WP_UnitTestCase {
public function test_run_bails_early_when_ai_disabled() {
$runner = $this->createMock( AJAX_Runner::class );
- $runner->method( 'should_use_ai' )->willReturn( false );
+ $runner->method( 'should_use_ai_name' )->willReturn( false );
$reflection = new \ReflectionClass( Plugin_Request_Utility::class );
$property = $reflection->getProperty( 'runner' );
From 6c8ecc01a431eb48d99812d5a05ab4aee2082f58 Mon Sep 17 00:00:00 2001
From: Ishita Jagtap <143935560+ishitaj34@users.noreply.github.com>
Date: Wed, 15 Jul 2026 21:04:53 +0530
Subject: [PATCH 07/11] fix: resolve phpmd warning
---
includes/Admin/Admin_AJAX.php | 1 +
includes/Checker/Abstract_Check_Runner.php | 1 +
2 files changed, 2 insertions(+)
diff --git a/includes/Admin/Admin_AJAX.php b/includes/Admin/Admin_AJAX.php
index 7a2a15979..cf9f87544 100644
--- a/includes/Admin/Admin_AJAX.php
+++ b/includes/Admin/Admin_AJAX.php
@@ -234,6 +234,7 @@ public function get_checks_to_run() {
$this->configure_runner( $runner );
$runner->set_categories( $categories );
$runner->set_use_ai( $use_ai );
+ $runner->set_use_ai_name( $use_ai_name );
$checks_to_run = $runner->get_checks_to_run();
} catch ( Exception $error ) {
diff --git a/includes/Checker/Abstract_Check_Runner.php b/includes/Checker/Abstract_Check_Runner.php
index 510e628b0..69c1ecf19 100644
--- a/includes/Checker/Abstract_Check_Runner.php
+++ b/includes/Checker/Abstract_Check_Runner.php
@@ -21,6 +21,7 @@
*
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
* @SuppressWarnings(PHPMD.TooManyPublicMethods)
+ * @SuppressWarnings(PHPMD.TooManyFields)
*/
abstract class Abstract_Check_Runner implements Check_Runner {
From ce4359f2ac32e55be103a281528f45be29cc1247 Mon Sep 17 00:00:00 2001
From: davidperezgar
Date: Sat, 22 Aug 2026 11:09:53 +0200
Subject: [PATCH 08/11] fix: remove leftover conflict markers
---
includes/Plugin_Main.php | 4 ----
1 file changed, 4 deletions(-)
diff --git a/includes/Plugin_Main.php b/includes/Plugin_Main.php
index cfe422fc3..4296f1aad 100644
--- a/includes/Plugin_Main.php
+++ b/includes/Plugin_Main.php
@@ -78,12 +78,8 @@ public function add_hooks() {
// Create the Settings page.
$settings_page = new Settings_Page();
$settings_page->add_hooks();
-<<<<<<< HEAD
-
// Create the SVN Checker page.
$svn_checker_page = new SVN_Checker_Page();
$svn_checker_page->add_hooks();
-=======
->>>>>>> 443d8a22 (feat: integrate Plugin Check Namer as standard AI Name Check static check)
}
}
From eb7084ff24ba0ef29d98e2948ee15277b09de425 Mon Sep 17 00:00:00 2001
From: davidperezgar
Date: Sat, 22 Aug 2026 11:59:24 +0200
Subject: [PATCH 09/11] finished
---
includes/Admin/Admin_AJAX.php | 18 ++++--------------
includes/Utilities/Plugin_Request_Utility.php | 15 +++++++++++++++
templates/admin-page.php | 6 +++---
3 files changed, 22 insertions(+), 17 deletions(-)
diff --git a/includes/Admin/Admin_AJAX.php b/includes/Admin/Admin_AJAX.php
index cf9f87544..b26fd2959 100644
--- a/includes/Admin/Admin_AJAX.php
+++ b/includes/Admin/Admin_AJAX.php
@@ -143,6 +143,10 @@ private function get_ajax_runner() {
return new WP_Error( 'invalid-runner', __( 'AJAX Runner was not initialized correctly.', 'plugin-check' ) );
}
+ // Register the runner so that checks relying on Plugin_Request_Utility::get_runner()
+ // (e.g. AI_Name_Check) can access it, even when no runtime checks are involved.
+ Plugin_Request_Utility::set_runner( $runner );
+
return $runner;
}
@@ -267,20 +271,6 @@ public function run_checks() {
wp_send_json_error( $runner, 500 );
}
- $runner = Plugin_Request_Utility::get_runner();
-
- if ( is_null( $runner ) ) {
- $runner = new AJAX_Runner();
- }
-
- // Make sure we are using the correct runner instance.
- if ( ! ( $runner instanceof AJAX_Runner ) ) {
- wp_send_json_error(
- new WP_Error( 'invalid-runner', __( 'AJAX Runner was not initialized correctly.', 'plugin-check' ) ),
- 500
- );
- }
-
$checks = filter_input( INPUT_POST, 'checks', FILTER_DEFAULT, FILTER_FORCE_ARRAY );
$checks = is_null( $checks ) ? array() : $checks;
$plugin = filter_input( INPUT_POST, 'plugin', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
diff --git a/includes/Utilities/Plugin_Request_Utility.php b/includes/Utilities/Plugin_Request_Utility.php
index 3451f8052..8958a982c 100644
--- a/includes/Utilities/Plugin_Request_Utility.php
+++ b/includes/Utilities/Plugin_Request_Utility.php
@@ -143,6 +143,21 @@ public static function get_runner() {
return null;
}
+ /**
+ * Sets the Runner class for the current request.
+ *
+ * This is needed for requests (e.g. AJAX requests running only static checks)
+ * where the runner is not registered early via the object-cache.php drop-in,
+ * so that checks relying on get_runner() (such as AI_Name_Check) can access it.
+ *
+ * @since x.x.x
+ *
+ * @param Abstract_Check_Runner $runner The runner instance for the current request.
+ */
+ public static function set_runner( Abstract_Check_Runner $runner ) {
+ static::$runner = $runner;
+ }
+
/**
* Runs the cleanup functions and destroys the runner.
*
diff --git a/templates/admin-page.php b/templates/admin-page.php
index 26183e659..e3ff17ee6 100644
--- a/templates/admin-page.php
+++ b/templates/admin-page.php
@@ -81,12 +81,12 @@
From 685e020b48b3239da8a32a856e0774fedc8b9637 Mon Sep 17 00:00:00 2001
From: davidperezgar
Date: Sat, 22 Aug 2026 12:16:13 +0200
Subject: [PATCH 10/11] fix: suppress PHPMD TooManyPublicMethods warning in
Plugin_Request_Utility
The new set_runner() method (added to support AI Name Check in AJAX-only
static check requests) pushed the public method count above PHPMD's
threshold. Suppressing the rule here follows the same pattern already used
in Admin_Page, Settings_Page, Abstract_Check_Runner and Check_Result.
---
includes/Utilities/Plugin_Request_Utility.php | 2 ++
1 file changed, 2 insertions(+)
diff --git a/includes/Utilities/Plugin_Request_Utility.php b/includes/Utilities/Plugin_Request_Utility.php
index 8958a982c..fd05763ec 100644
--- a/includes/Utilities/Plugin_Request_Utility.php
+++ b/includes/Utilities/Plugin_Request_Utility.php
@@ -16,6 +16,8 @@
* Class providing utility methods to return plugin information based on the request.
*
* @since 1.0.0
+ *
+ * @SuppressWarnings(PHPMD.TooManyPublicMethods)
*/
class Plugin_Request_Utility {
From c68b1e6afc2126707383672fd86e2faecb9e3a23 Mon Sep 17 00:00:00 2001
From: davidperezgar
Date: Fri, 4 Sep 2026 16:23:15 +0200
Subject: [PATCH 11/11] recover page
---
assets/js/plugin-check-admin.js | 19 -
assets/js/plugin-check-namer.js | 432 ++++++++++++++++
includes/Admin/Admin_AJAX.php | 4 -
includes/Admin/Namer_Page.php | 460 ++++++++++++++++++
includes/CLI/Plugin_Check_Command.php | 5 -
includes/Checker/Abstract_Check_Runner.php | 30 --
.../Checks/Plugin_Repo/AI_Name_Check.php | 4 +-
includes/Plugin_Main.php | 6 +
templates/admin-page.php | 7 +-
.../Checker/Checks/AI_Name_Check_Tests.php | 2 +-
10 files changed, 903 insertions(+), 66 deletions(-)
create mode 100644 assets/js/plugin-check-namer.js
create mode 100644 includes/Admin/Namer_Page.php
diff --git a/assets/js/plugin-check-admin.js b/assets/js/plugin-check-admin.js
index d0578264f..9b17043b3 100644
--- a/assets/js/plugin-check-admin.js
+++ b/assets/js/plugin-check-admin.js
@@ -38,7 +38,6 @@
'plugin-check__include-experimental'
);
const useAi = document.getElementById( 'plugin-check__use-ai' );
- const useAiName = document.getElementById( 'plugin-check__use-ai-name' );
// Handle disabling the Check it button when a plugin is not selected.
function canRunChecks() {
@@ -141,9 +140,6 @@
if ( useAi ) {
useAi.disabled = true;
}
- if ( useAiName ) {
- useAiName.disabled = true;
- }
if ( includeExperimental ) {
includeExperimental.disabled = true;
}
@@ -224,9 +220,6 @@
if ( useAi ) {
useAi.disabled = false;
}
- if ( useAiName ) {
- useAiName.disabled = false;
- }
if ( includeExperimental ) {
includeExperimental.disabled = false;
}
@@ -661,10 +654,6 @@
includeExperimentalInput
);
pluginCheckData.append( 'use-ai', useAiInput );
- pluginCheckData.append(
- 'use-ai-name',
- useAiName && useAiName.checked ? 1 : 0
- );
for ( let i = 0; i < checks.length; i++ ) {
pluginCheckData.append( 'checks[]', checks[ i ] );
@@ -729,10 +718,6 @@
includeExperimentalInput
);
pluginCheckData.append( 'use-ai', useAiInput );
- pluginCheckData.append(
- 'use-ai-name',
- useAiName && useAiName.checked ? 1 : 0
- );
for ( let i = 0; i < categories.length; i++ ) {
pluginCheckData.append( 'categories[]', categories[ i ] );
@@ -1025,10 +1010,6 @@
includeExperimentalInput
);
pluginCheckData.append( 'use-ai', useAiInput );
- pluginCheckData.append(
- 'use-ai-name',
- useAiName && useAiName.checked ? 1 : 0
- );
for ( let i = 0; i < types.length; i++ ) {
pluginCheckData.append( 'types[]', types[ i ] );
diff --git a/assets/js/plugin-check-namer.js b/assets/js/plugin-check-namer.js
new file mode 100644
index 000000000..f8e3d90cb
--- /dev/null
+++ b/assets/js/plugin-check-namer.js
@@ -0,0 +1,432 @@
+/**
+ * Plugin Check Namer tool.
+ */
+
+/* global pluginCheckNamer */
+
+( function () {
+ 'use strict';
+
+ function setText( el, text ) {
+ if ( ! el ) {
+ return;
+ }
+ el.textContent = text;
+ }
+
+ function setHtml( el, html ) {
+ if ( ! el ) {
+ return;
+ }
+ el.innerHTML = html;
+ }
+
+ function escapeHtml( text ) {
+ const div = document.createElement( 'div' );
+ div.textContent = text;
+ return div.innerHTML;
+ }
+
+ document.addEventListener( 'DOMContentLoaded', function () {
+ const form = document.getElementById( 'plugin-check-namer-form' );
+ const input = document.getElementById( 'plugin_check_namer_input' );
+
+ if ( ! form || ! input || ! window.pluginCheckNamer ) {
+ return;
+ }
+
+ const authorInput = document.getElementById(
+ 'plugin_check_namer_author'
+ );
+ const modelSelect = document.getElementById(
+ 'plugin_check_namer_model'
+ );
+
+ const submitBtn = document.getElementById(
+ 'plugin-check-namer-submit'
+ );
+ const spinner = document.getElementById( 'plugin-check-namer-spinner' );
+
+ const resultWrap = document.getElementById(
+ 'plugin-check-namer-result'
+ );
+ const verdictEl = document.getElementById(
+ 'plugin-check-namer-verdict'
+ );
+ const explainEl = document.getElementById(
+ 'plugin-check-namer-explanation'
+ );
+ const verdictContainer = document.getElementById(
+ 'plugin-check-namer-verdict-container'
+ );
+ const confusionPluginsDiv = document.getElementById(
+ 'plugin-check-namer-confusion-plugins'
+ );
+ const confusionPluginsList = document.getElementById(
+ 'plugin-check-namer-confusion-plugins-list'
+ );
+ const confusionOthersDiv = document.getElementById(
+ 'plugin-check-namer-confusion-others'
+ );
+ const confusionOthersList = document.getElementById(
+ 'plugin-check-namer-confusion-others-list'
+ );
+ const timingDiv = document.getElementById(
+ 'plugin-check-namer-timing'
+ );
+ const timingValue = document.getElementById(
+ 'plugin-check-namer-timing-value'
+ );
+ const tokensDiv = document.getElementById(
+ 'plugin-check-namer-tokens'
+ );
+ const tokensValue = document.getElementById(
+ 'plugin-check-namer-tokens-value'
+ );
+ const errorDiv = document.getElementById( 'plugin-check-namer-error' );
+ const errorEl = errorDiv ? errorDiv.querySelector( 'p' ) : null;
+
+ function setLoading( isLoading ) {
+ if ( spinner ) {
+ spinner.classList.toggle( 'is-active', isLoading );
+ }
+ submitBtn.disabled = isLoading;
+ }
+
+ form.addEventListener( 'submit', function ( event ) {
+ event.preventDefault();
+
+ const name = ( input.value || '' ).trim();
+ if ( ! name ) {
+ setText( errorEl, pluginCheckNamer.messages.missingName );
+ if ( errorEl ) {
+ errorEl.classList.remove( 'plugin-check-namer-hidden' );
+ }
+ return;
+ }
+
+ // Clear previous results.
+ if ( resultWrap ) {
+ resultWrap.classList.add( 'plugin-check-namer-hidden' );
+ }
+ if ( verdictEl ) {
+ setText( verdictEl, '' );
+ }
+ if ( explainEl ) {
+ setHtml( explainEl, '' );
+ }
+ if ( errorDiv ) {
+ errorDiv.classList.add( 'plugin-check-namer-hidden' );
+ }
+ if ( errorEl ) {
+ setText( errorEl, '' );
+ }
+ if ( confusionPluginsDiv ) {
+ confusionPluginsDiv.classList.add(
+ 'plugin-check-namer-hidden'
+ );
+ }
+ if ( confusionPluginsList ) {
+ confusionPluginsList.innerHTML = '';
+ }
+ if ( confusionOthersDiv ) {
+ confusionOthersDiv.classList.add( 'plugin-check-namer-hidden' );
+ }
+ if ( confusionOthersList ) {
+ confusionOthersList.innerHTML = '';
+ }
+ if ( timingDiv ) {
+ timingDiv.classList.add( 'plugin-check-namer-hidden' );
+ }
+ if ( timingValue ) {
+ setText( timingValue, '' );
+ }
+ if ( tokensDiv ) {
+ tokensDiv.classList.add( 'plugin-check-namer-hidden' );
+ }
+ if ( tokensValue ) {
+ setText( tokensValue, '' );
+ }
+ if ( verdictContainer ) {
+ verdictContainer.classList.add( 'plugin-check-namer-hidden' );
+ }
+
+ // Record start time.
+ const startTime = Date.now();
+
+ setLoading( true );
+
+ const formData = new FormData();
+ formData.append( 'action', 'plugin_check_namer_analyze' );
+ formData.append( 'nonce', pluginCheckNamer.nonce );
+ formData.append( 'plugin_name', name );
+ if ( authorInput ) {
+ formData.append(
+ 'author_name',
+ ( authorInput.value || '' ).trim()
+ );
+ }
+ if ( modelSelect ) {
+ formData.append(
+ 'model_preference',
+ ( modelSelect.value || '' ).trim()
+ );
+ }
+
+ fetch( pluginCheckNamer.ajaxUrl, {
+ method: 'POST',
+ credentials: 'same-origin',
+ body: formData,
+ } )
+ .then( function ( response ) {
+ return response.json();
+ } )
+ .then( function ( payload ) {
+ if ( ! payload || ! payload.success ) {
+ throw new Error(
+ payload && payload.data && payload.data.message
+ ? payload.data.message
+ : pluginCheckNamer.messages.genericError
+ );
+ }
+
+ setText( verdictEl, payload.data.verdict || '' );
+ setHtml( explainEl, payload.data.explanation || '' );
+
+ // Set border color based on verdict.
+ if ( verdictContainer ) {
+ const verdict = (
+ payload.data.verdict || ''
+ ).toLowerCase();
+ let borderColor = '#2271b1'; // Default blue.
+
+ if ( verdict.indexOf( 'disallowed' ) !== -1 ) {
+ borderColor = '#d63638'; // Red for disallowed.
+ } else if (
+ verdict.indexOf( 'good' ) !== -1 ||
+ verdict.indexOf( 'low' ) !== -1 ||
+ verdict.indexOf( 'no issues' ) !== -1
+ ) {
+ borderColor = '#00a32a'; // Green for good.
+ } else if (
+ verdict.indexOf( 'generally allowable' ) !== -1 ||
+ verdict.indexOf( 'allowable' ) !== -1
+ ) {
+ borderColor = '#2271b1'; // Blue for generally allowable.
+ } else if (
+ verdict.indexOf( 'review' ) !== -1 ||
+ verdict.indexOf( 'medium' ) !== -1 ||
+ verdict.indexOf( 'issues found' ) !== -1
+ ) {
+ borderColor = '#dba617'; // Yellow/orange for needs review.
+ } else if (
+ verdict.indexOf( 'problematic' ) !== -1 ||
+ verdict.indexOf( 'high' ) !== -1
+ ) {
+ borderColor = '#d63638'; // Red for problematic.
+ }
+
+ verdictContainer.style.borderLeftColor = borderColor;
+ verdictContainer.classList.remove(
+ 'plugin-check-namer-hidden'
+ );
+ }
+
+ // Calculate and display elapsed time.
+ if ( timingDiv && timingValue ) {
+ const endTime = Date.now();
+ const elapsedSeconds = Math.round(
+ ( endTime - startTime ) / 1000
+ );
+ timingValue.textContent =
+ elapsedSeconds + ' ' + 'seconds';
+ timingDiv.classList.remove(
+ 'plugin-check-namer-hidden'
+ );
+ }
+
+ // Display token usage if available.
+ if (
+ tokensDiv &&
+ tokensValue &&
+ payload.data.token_usage
+ ) {
+ const tokenUsage = payload.data.token_usage;
+ let tokensText = '';
+
+ // Add AI provider and model info if available.
+ if ( payload.data.ai_info ) {
+ const parts = [];
+ if ( payload.data.ai_info.provider ) {
+ parts.push( payload.data.ai_info.provider );
+ }
+ if ( payload.data.ai_info.model ) {
+ parts.push( payload.data.ai_info.model );
+ }
+ if ( parts.length ) {
+ tokensText = parts.join( ' / ' ) + ' - ';
+ }
+ }
+
+ const totalTokens =
+ tokenUsage.total_tokens ||
+ ( tokenUsage.prompt_tokens &&
+ tokenUsage.completion_tokens
+ ? tokenUsage.prompt_tokens +
+ tokenUsage.completion_tokens
+ : null );
+
+ if ( totalTokens ) {
+ tokensText += totalTokens + ' total';
+ }
+
+ // Add breakdown with prompt and completion tokens.
+ if (
+ tokenUsage.prompt_tokens &&
+ tokenUsage.completion_tokens
+ ) {
+ tokensText +=
+ ' (' +
+ tokenUsage.prompt_tokens +
+ ' prompt + ' +
+ tokenUsage.completion_tokens +
+ ' completion)';
+ }
+
+ // Add similar name query tokens if available.
+ if (
+ tokenUsage.similar_name &&
+ tokenUsage.similar_name.total_tokens
+ ) {
+ tokensText +=
+ ' [Similar name query: ' +
+ tokenUsage.similar_name.total_tokens +
+ ' tokens]';
+ }
+
+ tokensValue.textContent = tokensText;
+ tokensDiv.classList.remove(
+ 'plugin-check-namer-hidden'
+ );
+ }
+
+ // Display confusion_existing_plugins if available.
+ if (
+ confusionPluginsDiv &&
+ confusionPluginsList &&
+ payload.data.confusion_existing_plugins &&
+ payload.data.confusion_existing_plugins.length > 0
+ ) {
+ confusionPluginsList.innerHTML = '';
+ payload.data.confusion_existing_plugins.forEach(
+ function ( plugin ) {
+ const div = document.createElement( 'div' );
+ div.className =
+ 'plugin-check-namer-confusion-item';
+ div.innerHTML =
+ '' +
+ escapeHtml( plugin.name || '' ) +
+ '' +
+ ( plugin.active_installations
+ ? ' (' +
+ escapeHtml(
+ plugin.active_installations
+ ) +
+ ' ' +
+ 'active installs' +
+ ')'
+ : '' ) +
+ ( plugin.owner_username
+ ? ' - ' +
+ escapeHtml( plugin.owner_username ) +
+ ''
+ : '' ) +
+ '
' +
+ '' +
+ escapeHtml( plugin.explanation || '' ) +
+ '' +
+ ( plugin.link
+ ? '
' +
+ escapeHtml( plugin.link ) +
+ ''
+ : '' );
+ confusionPluginsList.appendChild( div );
+ }
+ );
+ confusionPluginsDiv.classList.remove(
+ 'plugin-check-namer-hidden'
+ );
+ } else if ( confusionPluginsDiv ) {
+ confusionPluginsDiv.classList.add(
+ 'plugin-check-namer-hidden'
+ );
+ }
+
+ // Display confusion_existing_others if available.
+ if (
+ confusionOthersDiv &&
+ confusionOthersList &&
+ payload.data.confusion_existing_others &&
+ payload.data.confusion_existing_others.length > 0
+ ) {
+ confusionOthersList.innerHTML = '';
+ payload.data.confusion_existing_others.forEach(
+ function ( item ) {
+ const div = document.createElement( 'div' );
+ div.className =
+ 'plugin-check-namer-confusion-item plugin-check-namer-confusion-item-others';
+ div.innerHTML =
+ '' +
+ escapeHtml( item.name || '' ) +
+ '' +
+ '
' +
+ '' +
+ escapeHtml( item.explanation || '' ) +
+ '' +
+ ( item.link
+ ? '
' +
+ escapeHtml( item.link ) +
+ ''
+ : '' );
+ confusionOthersList.appendChild( div );
+ }
+ );
+ confusionOthersDiv.classList.remove(
+ 'plugin-check-namer-hidden'
+ );
+ } else if ( confusionOthersDiv ) {
+ confusionOthersDiv.classList.add(
+ 'plugin-check-namer-hidden'
+ );
+ }
+
+ if ( resultWrap ) {
+ resultWrap.classList.remove(
+ 'plugin-check-namer-hidden'
+ );
+ }
+ } )
+ .catch( function ( err ) {
+ setText(
+ errorEl,
+ err && err.message
+ ? err.message
+ : pluginCheckNamer.messages.genericError
+ );
+
+ if ( errorDiv ) {
+ errorDiv.classList.remove(
+ 'plugin-check-namer-hidden'
+ );
+ }
+ } )
+ .finally( function () {
+ setLoading( false );
+ } );
+ } );
+ } );
+} )();
diff --git a/includes/Admin/Admin_AJAX.php b/includes/Admin/Admin_AJAX.php
index b26fd2959..aa529be85 100644
--- a/includes/Admin/Admin_AJAX.php
+++ b/includes/Admin/Admin_AJAX.php
@@ -227,7 +227,6 @@ public function get_checks_to_run() {
$checks = filter_input( INPUT_POST, 'checks', FILTER_DEFAULT, FILTER_FORCE_ARRAY );
$checks = is_null( $checks ) ? array() : $checks;
$use_ai = 1 === filter_input( INPUT_POST, 'use-ai', FILTER_VALIDATE_INT );
- $use_ai_name = 1 === filter_input( INPUT_POST, 'use-ai-name', FILTER_VALIDATE_INT );
$runner = $this->get_ajax_runner();
if ( is_wp_error( $runner ) ) {
@@ -238,7 +237,6 @@ public function get_checks_to_run() {
$this->configure_runner( $runner );
$runner->set_categories( $categories );
$runner->set_use_ai( $use_ai );
- $runner->set_use_ai_name( $use_ai_name );
$checks_to_run = $runner->get_checks_to_run();
} catch ( Exception $error ) {
@@ -277,7 +275,6 @@ public function run_checks() {
$include_experimental = 1 === filter_input( INPUT_POST, 'include-experimental', FILTER_VALIDATE_INT );
$use_ai = 1 === filter_input( INPUT_POST, 'use-ai', FILTER_VALIDATE_INT );
- $use_ai_name = 1 === filter_input( INPUT_POST, 'use-ai-name', FILTER_VALIDATE_INT );
$types = filter_input( INPUT_POST, 'types', FILTER_DEFAULT, FILTER_FORCE_ARRAY );
$types = is_null( $types ) ? array( 'error', 'warning' ) : $types;
@@ -286,7 +283,6 @@ public function run_checks() {
$runner->set_check_slugs( $checks );
$runner->set_plugin( $plugin );
$runner->set_use_ai( $use_ai );
- $runner->set_use_ai_name( $use_ai_name );
$results = $runner->run();
} catch ( Exception $error ) {
wp_send_json_error(
diff --git a/includes/Admin/Namer_Page.php b/includes/Admin/Namer_Page.php
new file mode 100644
index 000000000..cb4b0d415
--- /dev/null
+++ b/includes/Admin/Namer_Page.php
@@ -0,0 +1,460 @@
+hook_suffix = add_management_page(
+ __( 'Plugin Check Namer', 'plugin-check' ),
+ __( 'Plugin Check Namer', 'plugin-check' ),
+ 'manage_options',
+ self::MENU_SLUG,
+ array( $this, 'render_page' )
+ );
+ }
+
+ /**
+ * Enqueues scripts for the tools page.
+ *
+ * @since 1.8.0
+ *
+ * @param string $hook_suffix Current admin page hook suffix.
+ */
+ public function enqueue_scripts( $hook_suffix ) {
+ if ( $hook_suffix !== $this->hook_suffix ) {
+ return;
+ }
+
+ wp_enqueue_style(
+ 'plugin-check-admin',
+ plugins_url( 'assets/css/plugin-check-admin.css', WP_PLUGIN_CHECK_MAIN_FILE ),
+ array(),
+ WP_PLUGIN_CHECK_VERSION
+ );
+
+ wp_enqueue_script(
+ 'plugin-check-namer',
+ plugins_url( 'assets/js/plugin-check-namer.js', WP_PLUGIN_CHECK_MAIN_FILE ),
+ array(),
+ WP_PLUGIN_CHECK_VERSION,
+ true
+ );
+
+ wp_localize_script(
+ 'plugin-check-namer',
+ 'pluginCheckNamer',
+ array(
+ 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
+ 'nonce' => wp_create_nonce( 'plugin_check_namer_ajax' ),
+ 'messages' => array(
+ 'missingName' => __( 'Please enter a plugin name.', 'plugin-check' ),
+ 'genericError' => __( 'An unexpected error occurred.', 'plugin-check' ),
+ ),
+ )
+ );
+ }
+
+ /**
+ * AJAX handler to analyze a plugin name.
+ *
+ * @since 1.8.0
+ */
+ public function ajax_analyze() {
+ check_ajax_referer( 'plugin_check_namer_ajax', 'nonce' );
+
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'plugin-check' ) ) );
+ }
+
+ $name = $this->get_plugin_name_from_request();
+ if ( empty( $name ) ) {
+ wp_send_json_error( array( 'message' => __( 'Please enter a plugin name.', 'plugin-check' ) ) );
+ }
+
+ $author = $this->get_author_name_from_request();
+
+ $model_preference = $this->get_model_preference_from_request();
+ $ai_config = $this->get_ai_config( $model_preference );
+ if ( is_wp_error( $ai_config ) ) {
+ wp_send_json_error( array( 'message' => $ai_config->get_error_message() ) );
+ }
+
+ $analysis = $this->run_name_analysis( $ai_config['model_preference'], $name, $author );
+ if ( is_wp_error( $analysis ) ) {
+ wp_send_json_error( array( 'message' => $analysis->get_error_message() ) );
+ }
+
+ $parsed = $this->parse_analysis( $analysis );
+ $response = $this->build_ajax_response( $parsed, $analysis, $ai_config );
+
+ wp_send_json_success( $response );
+ }
+
+ /**
+ * Builds AJAX response from parsed analysis.
+ *
+ * @since 1.8.0
+ *
+ * @param array $parsed Parsed analysis.
+ * @param string|array $analysis Raw analysis.
+ * @param array $ai_config AI configuration with model preference info.
+ * @return array Response array.
+ */
+ protected function build_ajax_response( $parsed, $analysis, $ai_config = array() ) {
+ $raw_output = $this->get_raw_output( $parsed, $analysis );
+ $raw_output = $this->format_json_output( $raw_output );
+
+ $response = array(
+ 'verdict' => $parsed['verdict'],
+ 'explanation' => $parsed['explanation'],
+ 'raw' => $raw_output,
+ );
+
+ if ( ! empty( $parsed['confusion_existing_plugins'] ) ) {
+ $response['confusion_existing_plugins'] = $parsed['confusion_existing_plugins'];
+ }
+ if ( ! empty( $parsed['confusion_existing_others'] ) ) {
+ $response['confusion_existing_others'] = $parsed['confusion_existing_others'];
+ }
+ if ( ! empty( $parsed['token_usage'] ) ) {
+ $response['token_usage'] = $parsed['token_usage'];
+ }
+
+ // Add AI model preference information.
+ if ( ! empty( $ai_config['model_preference'] ) ) {
+ $response['ai_info'] = array(
+ 'model' => $ai_config['model_preference'],
+ );
+ }
+
+ return $response;
+ }
+
+ /**
+ * Gets plugin name from request.
+ *
+ * @since 1.8.0
+ *
+ * @return string Plugin name or empty string.
+ */
+ protected function get_plugin_name_from_request() {
+ $name = isset( $_POST['plugin_name'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin_name'] ) ) : '';
+ return trim( $name );
+ }
+
+ /**
+ * Gets author name from request.
+ *
+ * @since 1.8.0
+ *
+ * @return string Author name or empty string.
+ */
+ protected function get_author_name_from_request() {
+ $author = isset( $_POST['author_name'] ) ? sanitize_text_field( wp_unslash( $_POST['author_name'] ) ) : '';
+ return trim( $author );
+ }
+
+ /**
+ * Renders the page.
+ *
+ * @since 1.8.0
+ *
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ */
+ public function render_page() {
+ if ( ! current_user_can( 'manage_options' ) ) {
+ return;
+ }
+
+ $ai_config = $this->get_ai_config();
+ ?>
+
+
+
+
+
+
+ get_error_message() );
+ ?>
+
+
+
+ check_ai_prerequisites() && is_wp_error( $this->check_ai_connectors() ) ) {
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ get_model_preference_from_request();
+ $user_id = get_current_user_id();
+
+ if ( empty( $input ) ) {
+ $this->handle_analyze_error( $user_id, '', new WP_Error( 'missing_input', __( 'Please enter a plugin name.', 'plugin-check' ) ) );
+ return;
+ }
+
+ $ai_config = $this->get_ai_config( $model_preference );
+ if ( is_wp_error( $ai_config ) ) {
+ $this->handle_analyze_error( $user_id, $input, $ai_config );
+ return;
+ }
+
+ $analysis = $this->run_name_analysis( $ai_config['model_preference'], $input, $author );
+
+ if ( is_wp_error( $analysis ) ) {
+ $this->handle_analyze_error( $user_id, $input, $analysis );
+ return;
+ }
+
+ $this->store_result(
+ $user_id,
+ array(
+ 'input' => $input,
+ 'analysis' => $analysis,
+ )
+ );
+ wp_safe_redirect( $this->get_page_url() );
+ exit;
+ }
+
+ /**
+ * Handles analyze error and redirects.
+ *
+ * @since 1.8.0
+ *
+ * @param int $user_id User ID.
+ * @param string $input Input value.
+ * @param WP_Error $error Error object.
+ */
+ protected function handle_analyze_error( $user_id, $input, $error ) {
+ $this->store_result(
+ $user_id,
+ array(
+ 'input' => $input,
+ 'error' => $error,
+ )
+ );
+ wp_safe_redirect( $this->get_page_url() );
+ exit;
+ }
+
+ /**
+ * Gets the page URL.
+ *
+ * @since 1.8.0
+ *
+ * @return string
+ */
+ protected function get_page_url() {
+ return add_query_arg( array( 'page' => self::MENU_SLUG ), admin_url( 'tools.php' ) );
+ }
+}
diff --git a/includes/CLI/Plugin_Check_Command.php b/includes/CLI/Plugin_Check_Command.php
index a792e0bb7..fd10abbef 100644
--- a/includes/CLI/Plugin_Check_Command.php
+++ b/includes/CLI/Plugin_Check_Command.php
@@ -157,9 +157,6 @@ public function __construct( Plugin_Context $plugin_context ) {
* [--ai-model=]
* : AI model preference for analysis (e.g., 'openai::gpt-4o'). Requires --ai.
*
- * [--ai-name]
- * : Enable AI-based plugin name checking.
- *
* ## EXAMPLES
*
* wp plugin check akismet
@@ -204,7 +201,6 @@ public function check( $args, $assoc_args ) {
'mode' => 'new',
'ai' => false,
'ai-model' => '',
- 'ai-name' => false,
)
);
@@ -266,7 +262,6 @@ static function ( $dirs ) use ( $excluded_files ) {
$runner->set_slug( $options['slug'] );
$runner->set_mode( $options['mode'] );
$runner->set_use_ai( $options['ai'] );
- $runner->set_use_ai_name( $options['ai-name'] );
if ( ! empty( $options['ai-model'] ) ) {
$runner->set_ai_model_preference( $options['ai-model'] );
}
diff --git a/includes/Checker/Abstract_Check_Runner.php b/includes/Checker/Abstract_Check_Runner.php
index 69c1ecf19..664c2aafd 100644
--- a/includes/Checker/Abstract_Check_Runner.php
+++ b/includes/Checker/Abstract_Check_Runner.php
@@ -43,14 +43,6 @@ abstract class Abstract_Check_Runner implements Check_Runner {
*/
protected $use_ai = false;
- /**
- * Whether AI plugin name checking is enabled.
- *
- * @since x.x.x
- * @var bool
- */
- protected $use_ai_name = false;
-
/**
* AI model preference for analysis.
*
@@ -333,17 +325,6 @@ final public function set_use_ai( $use_ai ) {
$this->use_ai = (bool) $use_ai;
}
- /**
- * Sets whether to use AI plugin name check.
- *
- * @since x.x.x
- *
- * @param bool $use_ai_name True to enable AI name check, false to disable.
- */
- final public function set_use_ai_name( $use_ai_name ) {
- $this->use_ai_name = (bool) $use_ai_name;
- }
-
/**
* Sets the AI model preference for analysis.
*
@@ -367,17 +348,6 @@ public function should_use_ai() {
return $this->use_ai;
}
- /**
- * Determines if AI plugin name check should be used.
- *
- * @since x.x.x
- *
- * @return bool True if AI name check should be used, false otherwise.
- */
- public function should_use_ai_name() {
- return $this->use_ai_name;
- }
-
/**
* Gets the AI model preference for analysis.
*
diff --git a/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php b/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php
index 10aa17eab..c1433ff28 100644
--- a/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php
+++ b/includes/Checker/Checks/Plugin_Repo/AI_Name_Check.php
@@ -75,10 +75,10 @@ private function should_run_ai_check( $runner ) {
if ( ! $runner ) {
return false;
}
- if ( ! method_exists( $runner, 'should_use_ai_name' ) ) {
+ if ( ! method_exists( $runner, 'should_use_ai' ) ) {
return false;
}
- if ( ! $runner->should_use_ai_name() ) {
+ if ( ! $runner->should_use_ai() ) {
return false;
}
if ( is_wp_error( $this->check_ai_prerequisites() ) ) {
diff --git a/includes/Plugin_Main.php b/includes/Plugin_Main.php
index 4296f1aad..518c53a59 100644
--- a/includes/Plugin_Main.php
+++ b/includes/Plugin_Main.php
@@ -9,6 +9,7 @@
use WordPress\Plugin_Check\Admin\Admin_AJAX;
use WordPress\Plugin_Check\Admin\Admin_Page;
+use WordPress\Plugin_Check\Admin\Namer_Page;
use WordPress\Plugin_Check\Admin\Settings_Page;
use WordPress\Plugin_Check\Admin\SVN_Checker_Page;
@@ -78,6 +79,11 @@ public function add_hooks() {
// Create the Settings page.
$settings_page = new Settings_Page();
$settings_page->add_hooks();
+
+ // Create the Plugin Check Namer tool page.
+ $namer_page = new Namer_Page();
+ $namer_page->add_hooks();
+
// Create the SVN Checker page.
$svn_checker_page = new SVN_Checker_Page();
$svn_checker_page->add_hooks();
diff --git a/templates/admin-page.php b/templates/admin-page.php
index e3ff17ee6..d8bfe935e 100644
--- a/templates/admin-page.php
+++ b/templates/admin-page.php
@@ -81,12 +81,9 @@
diff --git a/tests/phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php b/tests/phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php
index 29be2abc0..759cd6d1f 100644
--- a/tests/phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php
+++ b/tests/phpunit/tests/Checker/Checks/AI_Name_Check_Tests.php
@@ -11,7 +11,7 @@ class AI_Name_Check_Tests extends \WP_UnitTestCase {
public function test_run_bails_early_when_ai_disabled() {
$runner = $this->createMock( AJAX_Runner::class );
- $runner->method( 'should_use_ai_name' )->willReturn( false );
+ $runner->method( 'should_use_ai' )->willReturn( false );
$reflection = new \ReflectionClass( Plugin_Request_Utility::class );
$property = $reflection->getProperty( 'runner' );