Everything in this course so far has been a manual discipline: read the competing pages yourself, run the gain check yourself, check the E-E-A-T checklist against a draft yourself. That’s the right way to learn it, and it’s also exactly the kind of repeatable, structured check that’s worth turning into a reusable ability, one that surfaces a specific, structured audit of a draft against information gain and E-E-A-T signals, for a human editor to act on. This lesson builds that ability, and it’s deliberately, permanently an analysis tool, not an auto-fix tool.
Course 1 for wp_register_ability(), Course 5 (or Course 6’s SEO Content Optimizer project) for
wp_ai_client_prompt() patterns, and Lesson 4’s content-review/extract-claims ability, which
this one is designed to sit alongside.
Step 1: Design the input and output shape
The audit needs the post’s actual content, a target keyword or topic to assess relevance against, and, optionally, a human-supplied summary of what the currently-ranking competing pages already cover, the same research a human would do manually in Lesson 2’s gain check. Supplying that context is optional because the ability should still return a useful E-E-A-T read even without it, information gain specifically needs that competitive context to mean anything.
// File: content-review/content-review.php
add_action( 'wp_abilities_api_init', function () {
wp_register_ability(
'content-review/audit-quality',
array(
'label' => __( 'Audit post for information gain and E-E-A-T', 'content-review' ),
'description' => __( 'Analyzes a post against a target topic, and optionally against a summary of already-ranking competing content, returning an information gain assessment, E-E-A-T signal checks, and specific suggestions. Read-only, does not modify the post.', 'content-review' ),
'category' => 'content',
'input_schema' => array(
'type' => 'object',
'properties' => array(
'post_id' => array( 'type' => 'integer' ),
'target_topic' => array( 'type' => 'string' ),
'competing_summary' => array(
'type' => 'string',
'description' => 'Optional: a human-written summary of what the top currently-ranking pages for this topic already cover.',
),
),
'required' => array( 'post_id', 'target_topic' ),
),
'output_schema' => array(
'type' => 'object',
'properties' => array(
'information_gain' => array( 'type' => 'string' ),
'eeat_signals' => array(
'type' => 'array',
'items' => array(
'type' => 'object',
'properties' => array(
'component' => array( 'type' => 'string' ),
'assessment' => array( 'type' => 'string' ),
),
),
),
'suggestions' => array(
'type' => 'array',
'items' => array( 'type' => 'string' ),
),
),
),
'permission_callback' => function ( $input ) {
return current_user_can( 'edit_post', $input['post_id'] );
},
'execute_callback' => 'content_review_audit_quality',
'meta' => array(
'annotations' => array( 'readonly' => true ),
'mcp' => array( 'public' => true ),
),
)
);
} );
Step 2: The execute_callback
This is where the audit prompt gets built and sent through the PHP AI Client SDK, then parsed
into the structured shape declared in output_schema.
// File: content-review/content-review.php
function content_review_audit_quality( $input ) {
$post = get_post( $input['post_id'] );
if ( ! $post ) {
return new WP_Error( 'not_found', 'No post found with that ID.' );
}
$topic = sanitize_text_field( $input['target_topic'] );
$body = wp_strip_all_tags( $post->post_content );
$competing = ! empty( $input['competing_summary'] )
? sanitize_textarea_field( $input['competing_summary'] )
: 'No summary of competing content was provided.';
$prompt = <<<PROMPT
Audit this draft for two things: information gain and E-E-A-T signals.
Topic: {$topic}
What competing, already-ranking pages on this topic cover:
{$competing}
Draft to audit:
{$body}
Part 1, information gain: state plainly whether this draft says something the competing
content described above does not already say, and name the specific gap or new angle if
one exists. If the draft mostly restates the competing content, say so directly.
Part 2, E-E-A-T signals: assess each of the four components separately, Experience,
Expertise, Authoritativeness, Trustworthiness, based only on what's visible in the draft
itself (a firsthand detail, a credential or technical depth, a citation or outbound link,
a named author or sourced claim). Note where a signal is present and where it's missing.
Part 3, suggestions: list specific, actionable changes that would improve information gain
or E-E-A-T, not generic advice.
PROMPT;
$response = wp_ai_client_prompt( $prompt )
->using_model_preference( 'anthropic/claude', 'openai/gpt', 'google/gemini' )
->generate_text();
if ( empty( $response ) ) {
return new WP_Error( 'empty_response', 'The AI provider returned no audit.' );
}
return content_review_parse_audit( $response );
}
function content_review_parse_audit( $response ) {
// Simple section-based parsing: split on the three part headers the prompt asked for.
$sections = preg_split( '/Part\s*\d+[,:]/i', $response );
$sections = array_values( array_filter( array_map( 'trim', $sections ) ) );
$information_gain = $sections[0] ?? '';
$eeat_raw = $sections[1] ?? '';
$suggestions_raw = $sections[2] ?? '';
$eeat_signals = array();
foreach ( array( 'Experience', 'Expertise', 'Authoritativeness', 'Trustworthiness' ) as $component ) {
if ( preg_match( '/' . preg_quote( $component, '/' ) . '[:\-]?\s*(.+?)(?=(Experience|Expertise|Authoritativeness|Trustworthiness)|$)/is', $eeat_raw, $m ) ) {
$eeat_signals[] = array(
'component' => $component,
'assessment' => trim( $m[1] ),
);
}
}
$suggestions = array();
foreach ( explode( "\n", $suggestions_raw ) as $line ) {
$line = trim( $line );
if ( preg_match( '/^[-*\d.]+\s*(.+)/', $line, $m ) ) {
$suggestions[] = $m[1];
}
}
return array(
'information_gain' => $information_gain,
'eeat_signals' => $eeat_signals,
'suggestions' => $suggestions,
);
}
sanitize_textarea_field() handles the multi-line competing_summary input correctly, since
sanitize_text_field() alone would collapse it to a single line. The parsing here, like Course
6’s SEO optimizer, is intentionally simple: it’s splitting on the structure the prompt itself
asked for, not attempting general-purpose natural language parsing.
Step 3: Why this stays read-only, permanently
This ability never calls wp_update_post(), and that’s not a limitation to remove in a later
version, it’s the entire point. An audit that flags “this draft lacks a named author bio” or “no
outbound citation supports this claim” is exactly the kind of finding a human needs to act on
with real judgment, adding a genuine author bio, finding and linking a real source. None of that
is safely automatable, and auto-applying a model’s own suggested fix for a trustworthiness gap
would just replace one unverified claim with another. meta.annotations.readonly reflects that
this ability’s entire value is in surfacing the audit, not resolving it.
It’s tempting to extend this into an ability that automatically rewrites the draft to address its own findings. If you do build that, make sure it still stops at a draft state a human reviews and explicitly approves before publish, never auto-applies to a live post. The whole premise of this course is that quality signals like real experience and real citations can’t be faked by the same model that flagged them missing.
Test it
wp-env run cli wp eval '
$result = wp_get_ability( "content-review/audit-quality" )->execute( array(
"post_id" => 42,
"target_topic" => "wordpress ai content workflows",
"competing_summary" => "Top pages define AI content risk generically and recommend disclosure, none cite a specific editorial workflow or named author.",
) );
print_r( $result );
'
You should see a non-empty information_gain assessment referencing your actual draft and the
competing summary you provided, an eeat_signals array covering all four components, and a
suggestions array with specific, actionable items, not generic filler.
Recap
The content-review/audit-quality ability combines the two manual disciplines from earlier in
this course, the Lesson 2 gain check and the Lesson 3 E-E-A-T checklist, into one structured,
repeatable audit backed by wp_ai_client_prompt(). It takes a post, a topic, and optional
competing-content context, and returns a specific information gain read, a per-component E-E-A-T
assessment, and concrete suggestions. It stays permanently read-only alongside Lesson 4’s
content-review/extract-claims ability, both exist to hand a human editor a clearer, more
structured starting point, neither one replaces the editorial judgment this whole course has
argued is where quality actually comes from.
Resources & further reading
- wp_register_ability() function reference, developer.wordpress.org
- sanitize_textarea_field() function reference, developer.wordpress.org
- PHP AI Client SDK repository, GitHub
- Project: SEO Content Optimizer, this track, Course 6