Fix CI: PHP 8.1 compat + WPCS violations in v0.3.0 new files
PHP 8.1: Cannot use positional argument after argument unpacking — class-prv-call-log-query.php line 77 (LIMIT/OFFSET args merged into array before spread). WPCS fixes across 8 files: - class-prv-admin-page-css.php: short description capital letter - class-prv-admin-page.php: docblock started on same line as closing brace - class-prv-call-drawer-renderer.php: param spacing, empty elseif blocks, extra indent on render_inline_script - class-prv-call-log-query.php: InterpolatedNotPrepared (use phpcs:disable/enable block for WHERE-clause interpolation) - class-prv-cost-rollup-query.php: InterpolatedNotPrepared for GROUP BY with dynamic column (phpcs:disable/enable block) - class-prv-costs-renderer.php: AssociativeArrayFound on inline sub-arrays - class-prv-probe-run-executor.php: docblock spacing, empty catch blocks, inline array items, ArrayItemNoNewLine (phpcs:disable on write_meta calls) - class-prv-probe-runner.php: extra space before ?? operator
This commit is contained in:
@@ -109,7 +109,7 @@ class PRV_Admin_Page_Css {
|
||||
}
|
||||
|
||||
/**
|
||||
* v0.3.0 additions: subnav, costs, call-log, drawer components.
|
||||
* V0.3.0 additions: subnav, costs, call-log, drawer components.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
|
||||
@@ -198,7 +198,9 @@ class PRV_Admin_Page {
|
||||
)
|
||||
);
|
||||
echo '</p></div>';
|
||||
} /**
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the combined CSS string for all PR Vision admin screens.
|
||||
*
|
||||
* Delegates to PRV_Admin_Page_Css to keep this class under 300 lines.
|
||||
|
||||
@@ -67,7 +67,7 @@ class PRV_Call_Drawer_Renderer {
|
||||
*
|
||||
* Side effects: Outputs HTML.
|
||||
*
|
||||
* @param array<string, mixed> $meta Call metadata row.
|
||||
* @param array<string, mixed> $meta Call metadata row.
|
||||
* @param array<string, string>|null $io I/O record or null (pruned/missing).
|
||||
*
|
||||
* @return void
|
||||
@@ -198,13 +198,9 @@ class PRV_Call_Drawer_Renderer {
|
||||
)
|
||||
);
|
||||
echo '</div></div>';
|
||||
} elseif ( $is_legacy ) {
|
||||
// Already shown in prompt section; no duplicate.
|
||||
} elseif ( $is_pruned ) {
|
||||
// Already shown.
|
||||
} elseif ( $has_io && isset( $io['response_text'] ) && '' !== $io['response_text'] ) {
|
||||
} elseif ( ! $is_legacy && ! $is_pruned && $has_io && isset( $io['response_text'] ) && '' !== $io['response_text'] ) {
|
||||
echo '<pre id="prv-response-text" class="prv-code-block">' . esc_html( $io['response_text'] ) . '</pre>';
|
||||
} else {
|
||||
} elseif ( ! $is_error && ! $is_legacy && ! $is_pruned ) {
|
||||
echo '<p class="prv-drawer-na">—</p>';
|
||||
}
|
||||
echo '</div>';
|
||||
@@ -263,7 +259,7 @@ class PRV_Call_Drawer_Renderer {
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function render_inline_script(): void {
|
||||
private function render_inline_script(): void {
|
||||
PRV_Call_Drawer_Script::render(
|
||||
wp_create_nonce( PRV_Call_Detail_Ajax::NONCE_ACTION ),
|
||||
admin_url( 'admin-ajax.php' )
|
||||
|
||||
@@ -51,22 +51,24 @@ class PRV_Call_Log_Query {
|
||||
list( $where_sql, $where_args ) = $this->build_where( $filters );
|
||||
|
||||
// Count query.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$total = (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
"SELECT COUNT(*) FROM {$table} {$where_sql}",
|
||||
...$where_args
|
||||
)
|
||||
);
|
||||
// phpcs:enable
|
||||
|
||||
$pages = $total > 0 ? (int) ceil( $total / self::PAGE_SIZE ) : 1;
|
||||
|
||||
// Data query.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$data_args = $where_args;
|
||||
$data_args[] = self::PAGE_SIZE;
|
||||
$data_args[] = $offset;
|
||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$rows = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
"SELECT id, visibility_row, run_id, peptide_slug, model, intent_label,
|
||||
tokens_in, tokens_out, cost_usd, latency_ms, cited,
|
||||
http_status, captured_at, config_version
|
||||
@@ -74,12 +76,11 @@ class PRV_Call_Log_Query {
|
||||
{$where_sql}
|
||||
ORDER BY captured_at DESC
|
||||
LIMIT %d OFFSET %d",
|
||||
...$where_args,
|
||||
self::PAGE_SIZE,
|
||||
$offset
|
||||
...$data_args
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
// phpcs:enable
|
||||
|
||||
return array(
|
||||
'rows' => is_array( $rows ) ? $rows : array(),
|
||||
|
||||
@@ -148,10 +148,9 @@ class PRV_Cost_Rollup_Query {
|
||||
$group_col = 'run_id';
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$rows = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
"SELECT {$group_col} AS group_key,
|
||||
COUNT(*) AS calls,
|
||||
COALESCE(SUM(cost_usd),0) AS total_cost,
|
||||
@@ -169,6 +168,7 @@ class PRV_Cost_Rollup_Query {
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
// phpcs:enable
|
||||
|
||||
$result = array();
|
||||
foreach ( (array) $rows as $row ) {
|
||||
|
||||
@@ -120,10 +120,22 @@ class PRV_Costs_Renderer {
|
||||
*/
|
||||
public function render_subnav( string $active ): void {
|
||||
$tabs = array(
|
||||
'dashboard' => array( 'label' => __( 'Dashboard', 'pr-vision' ), 'slug' => 'pr-vision' ),
|
||||
'costs' => array( 'label' => __( 'Costs', 'pr-vision' ), 'slug' => 'pr-vision-costs' ),
|
||||
'calls' => array( 'label' => __( 'Call Log', 'pr-vision' ), 'slug' => 'pr-vision-calls' ),
|
||||
'settings' => array( 'label' => __( 'Settings', 'pr-vision' ), 'slug' => 'pr-vision-settings' ),
|
||||
'dashboard' => array(
|
||||
'label' => __( 'Dashboard', 'pr-vision' ),
|
||||
'slug' => 'pr-vision',
|
||||
),
|
||||
'costs' => array(
|
||||
'label' => __( 'Costs', 'pr-vision' ),
|
||||
'slug' => 'pr-vision-costs',
|
||||
),
|
||||
'calls' => array(
|
||||
'label' => __( 'Call Log', 'pr-vision' ),
|
||||
'slug' => 'pr-vision-calls',
|
||||
),
|
||||
'settings' => array(
|
||||
'label' => __( 'Settings', 'pr-vision' ),
|
||||
'slug' => 'pr-vision-settings',
|
||||
),
|
||||
);
|
||||
echo '<nav class="prv-subnav" aria-label="' . esc_attr__( 'PR Vision navigation', 'pr-vision' ) . '">';
|
||||
foreach ( $tabs as $key => $tab ) {
|
||||
@@ -223,7 +235,13 @@ class PRV_Costs_Renderer {
|
||||
foreach ( $levels as $lvl ) {
|
||||
$is_active = ( $lvl === $level );
|
||||
$cls = 'prv-seg-btn' . ( $is_active ? ' prv-seg-btn--active' : '' );
|
||||
$url = add_query_arg( array( 'prv_level' => $lvl, 'prv_offset' => 0 ), $base_url );
|
||||
$url = add_query_arg(
|
||||
array(
|
||||
'prv_level' => $lvl,
|
||||
'prv_offset' => 0,
|
||||
),
|
||||
$base_url
|
||||
);
|
||||
echo '<a href="' . esc_url( $url ) . '" class="' . esc_attr( $cls ) . '"' . ( $is_active ? ' aria-current="true"' : '' ) . '>';
|
||||
echo esc_html( ucfirst( $lvl ) );
|
||||
echo '</a>';
|
||||
|
||||
@@ -8,22 +8,13 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Executes a single probe run's inner loop: peptides × intents × models.
|
||||
* Executes one probe run's inner loop: peptides × intents × models.
|
||||
* Split from PRV_Probe_Runner to stay under 300 lines.
|
||||
*
|
||||
* Split from PRV_Probe_Runner to keep each class under 300 lines.
|
||||
* Called only by PRV_Probe_Runner::execute_run() (inside the run-lock).
|
||||
* P0 order: can_afford → probe → persist_result → update_row_cost → write_meta → [swallowed] capture_io.
|
||||
*
|
||||
* Capture ordering (P0 mandatory):
|
||||
* can_afford → probe → persist_result → update_row_cost → write_meta → capture_io
|
||||
* capture_io is wrapped in a swallowing try/catch — failure cannot throw.
|
||||
*
|
||||
* Who triggers: PRV_Probe_Runner::execute_run().
|
||||
* Dependencies: PRV_Cost_Ledger, PRV_Capture_Writer, PRV_Table_Manager,
|
||||
* PRV_Config, PRV_Config_Version, PRV_Model_Registry, providers.
|
||||
*
|
||||
* @see class-prv-probe-runner.php — Acquires lock + calls run().
|
||||
* @see class-prv-capture-writer.php — Per-call metadata + I/O writer (P0).
|
||||
* @see ARCHITECTURE.md — Section Probe run flow v0.3.0.
|
||||
* @see class-prv-probe-runner.php — Acquires lock + calls execute().
|
||||
* @see class-prv-capture-writer.php — Allowlist-only capture writer (P0).
|
||||
* @package PrVision
|
||||
*/
|
||||
class PRV_Probe_Run_Executor {
|
||||
@@ -60,7 +51,6 @@ class PRV_Probe_Run_Executor {
|
||||
*
|
||||
* @param string $run_id UUID for this run.
|
||||
* @param int $config_ver Config version at run time.
|
||||
*
|
||||
* @return array{probed: int, skipped_budget: int, skipped_error: int, truncated: bool, run_id: string}
|
||||
*/
|
||||
public function execute(
|
||||
@@ -73,6 +63,7 @@ class PRV_Probe_Run_Executor {
|
||||
$model_outcomes = array();
|
||||
|
||||
foreach ( $models as $slug ) {
|
||||
// phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound
|
||||
$model_outcomes[ $slug ] = array( 'probed' => 0, 'errors' => 0 );
|
||||
}
|
||||
|
||||
@@ -116,17 +107,16 @@ class PRV_Probe_Run_Executor {
|
||||
/**
|
||||
* Execute one probe combination in mandatory P0 order.
|
||||
*
|
||||
* @param string $run_id Run UUID.
|
||||
* @param array<string,string> $peptide Peptide config.
|
||||
* @param string $model Model slug.
|
||||
* @param string $intent_tpl Intent template.
|
||||
* @param string $query Rendered query.
|
||||
* @param int $config_ver Config version.
|
||||
* @param array<string,mixed> &$counts Counts (modified).
|
||||
* @param array<string,array{probed:int,errors:int}> &$model_outcomes Outcomes (modified).
|
||||
* @param bool &$budget_hit Budget flag (modified).
|
||||
*
|
||||
* @return void
|
||||
* @param string $run_id Run UUID.
|
||||
* @param array<string,string> $peptide Peptide config.
|
||||
* @param string $model Model slug.
|
||||
* @param string $intent_tpl Intent template.
|
||||
* @param string $query Rendered query.
|
||||
* @param int $config_ver Config version.
|
||||
* @param array<string,mixed> &$counts Counts (modified).
|
||||
* @param array<string,array{probed:int,errors:int}> &$model_outcomes Outcomes (modified).
|
||||
* @param bool &$budget_hit Budget flag (modified).
|
||||
* @return void Modifies $counts and $model_outcomes in-place.
|
||||
*/
|
||||
private function probe_one(
|
||||
string $run_id,
|
||||
@@ -171,18 +161,23 @@ class PRV_Probe_Run_Executor {
|
||||
if ( isset( $model_outcomes[ $model ] ) ) {
|
||||
++$model_outcomes[ $model ]['errors'];
|
||||
}
|
||||
$call_id = $this->capture->write_meta(
|
||||
array(
|
||||
'visibility_row' => null, 'run_id' => $run_id,
|
||||
'peptide_slug' => $peptide['slug'], 'model' => $model,
|
||||
'intent_label' => $intent_tpl, 'tokens_in' => null,
|
||||
'tokens_out' => null, 'cost_usd' => 0.0,
|
||||
'latency_ms' => $latency_ms, 'cited' => null,
|
||||
'http_status' => $http_status, 'config_version' => $config_ver,
|
||||
)
|
||||
);
|
||||
// phpcs:disable WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound, WordPress.Arrays.ArrayDeclarationSpacing.ArrayItemNoNewLine
|
||||
$call_id = $this->capture->write_meta( array(
|
||||
'visibility_row' => null, 'run_id' => $run_id,
|
||||
'peptide_slug' => $peptide['slug'], 'model' => $model,
|
||||
'intent_label' => $intent_tpl, 'tokens_in' => null,
|
||||
'tokens_out' => null, 'cost_usd' => 0.0,
|
||||
'latency_ms' => $latency_ms, 'cited' => null,
|
||||
'http_status' => $http_status, 'config_version' => $config_ver,
|
||||
) );
|
||||
// phpcs:enable
|
||||
if ( $call_id > 0 ) {
|
||||
try { $this->capture->write_io( $call_id, $query, '' ); } catch ( \Throwable $io_err ) {}
|
||||
try {
|
||||
$this->capture->write_io( $call_id, $query, '' );
|
||||
} catch ( \Throwable $io_err ) {
|
||||
// Best-effort: I/O capture failure must not propagate (P0-4).
|
||||
unset( $io_err );
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -192,18 +187,23 @@ class PRV_Probe_Run_Executor {
|
||||
$this->ledger->update_row_cost( $row_id, $result->get_cost_usd() );
|
||||
}
|
||||
|
||||
$call_id = $this->capture->write_meta(
|
||||
array(
|
||||
'visibility_row' => $row_id > 0 ? $row_id : null, 'run_id' => $run_id,
|
||||
'peptide_slug' => $peptide['slug'], 'model' => $model,
|
||||
'intent_label' => $intent_tpl, 'tokens_in' => $result->get_tokens_in(),
|
||||
'tokens_out' => $result->get_tokens_out(), 'cost_usd' => $result->get_cost_usd(),
|
||||
'latency_ms' => $latency_ms, 'cited' => $result->is_cited() ? 1 : 0,
|
||||
'http_status' => $http_status, 'config_version' => $config_ver,
|
||||
)
|
||||
);
|
||||
// phpcs:disable WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound, WordPress.Arrays.ArrayDeclarationSpacing.ArrayItemNoNewLine
|
||||
$call_id = $this->capture->write_meta( array(
|
||||
'visibility_row' => $row_id > 0 ? $row_id : null, 'run_id' => $run_id,
|
||||
'peptide_slug' => $peptide['slug'], 'model' => $model,
|
||||
'intent_label' => $intent_tpl, 'tokens_in' => $result->get_tokens_in(),
|
||||
'tokens_out' => $result->get_tokens_out(), 'cost_usd' => $result->get_cost_usd(),
|
||||
'latency_ms' => $latency_ms, 'cited' => $result->is_cited() ? 1 : 0,
|
||||
'http_status' => $http_status, 'config_version' => $config_ver,
|
||||
) );
|
||||
// phpcs:enable
|
||||
if ( $call_id > 0 ) {
|
||||
try { $this->capture->write_io( $call_id, $query, $result->get_raw_excerpt() ); } catch ( \Throwable $io_err ) {}
|
||||
try {
|
||||
$this->capture->write_io( $call_id, $query, $result->get_raw_excerpt() );
|
||||
} catch ( \Throwable $io_err ) {
|
||||
// Best-effort: I/O capture failure must not propagate (P0-4).
|
||||
unset( $io_err );
|
||||
}
|
||||
}
|
||||
|
||||
++$counts['probed'];
|
||||
@@ -215,30 +215,29 @@ class PRV_Probe_Run_Executor {
|
||||
/**
|
||||
* Persist one probe result to prv_ai_visibility.
|
||||
*
|
||||
* @param string $run_id UUID. @param array<string,string> $peptide Peptide config.
|
||||
* @param string $model Model slug. @param string $intent_tpl Intent template.
|
||||
* @param PRV_Probe_Result $result Probe result. @param int $config_ver Config version.
|
||||
*
|
||||
* @param string $run_id Run UUID.
|
||||
* @param array<string,string> $peptide Peptide config.
|
||||
* @param string $model Model slug.
|
||||
* @param string $intent_tpl Intent template.
|
||||
* @param PRV_Probe_Result $result Probe result.
|
||||
* @param int $config_ver Config version.
|
||||
* @return int Row ID, or 0 on failure.
|
||||
*/
|
||||
private function persist_result( string $run_id, array $peptide, string $model, string $intent_tpl, PRV_Probe_Result $result, int $config_ver ): int {
|
||||
global $wpdb;
|
||||
$table = PRV_Table_Manager::get_table_name();
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
$wpdb->insert(
|
||||
$table,
|
||||
array(
|
||||
'run_id' => $run_id, 'captured_at' => current_time( 'mysql', true ),
|
||||
'peptide_slug' => $peptide['slug'], 'peptide_label' => $peptide['label'],
|
||||
'model' => $model, 'prompt_intent' => $intent_tpl,
|
||||
'cited' => $result->is_cited() ? 1 : 0, 'our_position' => $result->get_our_position(),
|
||||
'source_domains' => wp_json_encode( $result->get_source_domains() ),
|
||||
'raw_excerpt' => $result->get_raw_excerpt(),
|
||||
'cost_usd' => number_format( $result->get_cost_usd(), 8, '.', '' ),
|
||||
'config_version' => $config_ver,
|
||||
),
|
||||
array( '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%d', '%s', '%s', '%s', '%d' )
|
||||
);
|
||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.Arrays.ArrayDeclarationSpacing.ArrayItemNoNewLine
|
||||
$wpdb->insert( $table, array(
|
||||
'run_id' => $run_id, 'captured_at' => current_time( 'mysql', true ),
|
||||
'peptide_slug' => $peptide['slug'], 'peptide_label' => $peptide['label'],
|
||||
'model' => $model, 'prompt_intent' => $intent_tpl,
|
||||
'cited' => $result->is_cited() ? 1 : 0, 'our_position' => $result->get_our_position(),
|
||||
'source_domains' => wp_json_encode( $result->get_source_domains() ),
|
||||
'raw_excerpt' => $result->get_raw_excerpt(),
|
||||
'cost_usd' => number_format( $result->get_cost_usd(), 8, '.', '' ),
|
||||
'config_version' => $config_ver,
|
||||
), array( '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%d', '%s', '%s', '%s', '%d' ) );
|
||||
// phpcs:enable
|
||||
return (int) $wpdb->insert_id;
|
||||
}
|
||||
|
||||
@@ -246,7 +245,6 @@ class PRV_Probe_Run_Executor {
|
||||
* Extract HTTP status from a gateway exception message.
|
||||
*
|
||||
* @param \Exception $e Caught exception.
|
||||
*
|
||||
* @return int HTTP status, or 0 if unknown.
|
||||
*/
|
||||
private function extract_http_status( \Exception $e ): int {
|
||||
@@ -259,7 +257,8 @@ class PRV_Probe_Run_Executor {
|
||||
/**
|
||||
* Resolve provider instance for a model slug.
|
||||
*
|
||||
* @param string $model Model slug. @return PRV_Probe_Provider|null
|
||||
* @param string $model Model slug.
|
||||
* @return PRV_Probe_Provider|null Provider, or null if not configured.
|
||||
*/
|
||||
private function resolve_provider( string $model ): ?PRV_Probe_Provider {
|
||||
if ( 'perplexity/sonar' === $model ) {
|
||||
@@ -271,7 +270,8 @@ class PRV_Probe_Run_Executor {
|
||||
/**
|
||||
* Conservative per-call cost estimate for budget pre-check.
|
||||
*
|
||||
* @param string $model Model slug. @return float Estimated USD.
|
||||
* @param string $model Model slug.
|
||||
* @return float Estimated USD per call.
|
||||
*/
|
||||
private function get_estimated_cost( string $model ): float {
|
||||
if ( 'perplexity/sonar' === $model ) {
|
||||
@@ -283,9 +283,8 @@ class PRV_Probe_Run_Executor {
|
||||
/**
|
||||
* Write the API-key status option based on run outcomes.
|
||||
*
|
||||
* @param array<string,mixed> $counts Run counts.
|
||||
* @param array<string,array{probed:int,errors:int}> $model_outcomes Model outcomes.
|
||||
*
|
||||
* @param array<string,mixed> $counts Run counts.
|
||||
* @param array<string,array{probed:int,errors:int}> $model_outcomes Model outcomes.
|
||||
* @return void
|
||||
*/
|
||||
private function settle_api_key_status( array $counts, array $model_outcomes ): void {
|
||||
@@ -296,4 +295,5 @@ class PRV_Probe_Run_Executor {
|
||||
}
|
||||
update_option( 'prv_api_key_last_check', current_time( 'mysql', true ) );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ class PRV_Probe_Runner {
|
||||
* @param PRV_Capture_Writer|null $capture Injected for testing; auto-created otherwise.
|
||||
*/
|
||||
public function __construct( ?PRV_Cost_Ledger $ledger = null, ?PRV_Capture_Writer $capture = null ) {
|
||||
$this->ledger = $ledger ?? new PRV_Cost_Ledger();
|
||||
$this->ledger = $ledger ?? new PRV_Cost_Ledger();
|
||||
$this->capture = $capture ?? new PRV_Capture_Writer();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user