Files
prautoblogger/prautoblogger.php
peptiderepo 77ef941b3d fix: document STATUS_TTL/API_TIMEOUT/MAX_RETRIES safety invariant (v0.18.3)
Add comment at the PRAUTOBLOGGER_MAX_RETRIES + PRAUTOBLOGGER_API_TIMEOUT_SECONDS
constant definitions documenting the dependency:

  API_TIMEOUT_SECONDS × MAX_RETRIES (540s) must stay < STATUS_TTL (600s)

Current product: 180s × 3 = 540s — 60s margin. Prevents constant-drift
regression: raising either constant without checking the inequality could cause
a fully-retrying LLM call to exhaust the status-transient TTL within a single
stage, triggering a false orphan-run abort (R2b path in poller).

Agent-Session: prab-v0183-20260612
2026-06-12 03:51:43 +00:00

161 lines
7.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* phpcs:ignore Generic.PHP.RequireStrictTypes.MissingDeclaration -- strict_types must precede docblock
*
* PRAutoBlogger — Automated Content Research & Publishing
*
* @wordpress-plugin
* Plugin Name: PRAutoBlogger
* Plugin URI: https://peptiderepo.com/prautoblogger
* Description: Monitors social media for trending topics, generates SEO-friendly blog posts using AI, and publishes them on a daily schedule with full cost tracking and self-improvement metrics.
* Version: 0.18.3
* Requires at least: 6.0
* Requires PHP: 7.4
* Author: PeptideRepo
* Author URI: https://peptiderepo.com
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: prautoblogger
* Domain Path: /languages
*
* @see ARCHITECTURE.md — Full data flow and file tree
* @see CONVENTIONS.md — Naming patterns and extension guides
*/
declare(strict_types=1);
// Abort if called directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/*
|--------------------------------------------------------------------------
| Plugin Constants
|--------------------------------------------------------------------------
| Defined here so every file in the plugin can reference paths, versions,
| and limits without magic strings.
*/
define( 'PRAUTOBLOGGER_VERSION', '0.18.3' );
define( 'PRAUTOBLOGGER_DB_VERSION', '1.2.0' );
define( 'PRAUTOBLOGGER_PLUGIN_FILE', __FILE__ );
define( 'PRAUTOBLOGGER_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'PRAUTOBLOGGER_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
define( 'PRAUTOBLOGGER_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
// API and retry limits — named constants instead of magic numbers.
// SAFETY INVARIANT: API_TIMEOUT_SECONDS × MAX_RETRIES must stay < STATUS_TTL (600s,
// defined in PRAutoBlogger_Generation_Status_Poller). The current product
// (180s × 3 = 540s) leaves a 60s margin. If either constant is raised, verify the
// inequality still holds — otherwise a fully-retrying LLM call can exhaust the
// status transient TTL within a single stage and trigger a false orphan-run abort.
define( 'PRAUTOBLOGGER_MAX_RETRIES', 3 );
// Pipeline v2 Phase 1 (v0.18.0) defaults. Both are SETTINGS-backed at every
// read site (prautoblogger_per_run_cost_ceiling_usd /
// prautoblogger_request_json_retention_days); these constants exist only as
// the named get_option() fallbacks — never inline the literals.
define( 'PRAUTOBLOGGER_DEFAULT_RUN_CEILING_USD', 0.50 );
define( 'PRAUTOBLOGGER_DEFAULT_REQUEST_JSON_RETENTION_DAYS', 14 );
// v0.18.1: ceiling on "thinking" tokens per reasoning-enabled LLM call. The
// request's completion budget (max_tokens) is RAISED by this amount so
// reasoning can never consume the visible-content budget and return an empty
// message (the 2026-06-11 empty-draft incident). SETTINGS-backed
// (prautoblogger_reasoning_max_tokens); 0 disables the cap (pure effort mode).
define( 'PRAUTOBLOGGER_DEFAULT_REASONING_MAX_TOKENS', 2048 );
define( 'PRAUTOBLOGGER_RETRY_BASE_DELAY_SECONDS', 2 );
define( 'PRAUTOBLOGGER_API_TIMEOUT_SECONDS', 180 );
define( 'PRAUTOBLOGGER_CACHE_TTL_SECONDS', 3600 );
// Default models for OpenRouter (user can override in settings).
define( 'PRAUTOBLOGGER_DEFAULT_ANALYSIS_MODEL', 'google/gemini-2.5-flash-lite' );
define( 'PRAUTOBLOGGER_DEFAULT_WRITING_MODEL', 'google/gemini-2.5-flash-lite' );
define( 'PRAUTOBLOGGER_DEFAULT_EDITOR_MODEL', 'google/gemini-2.5-flash-lite' );
// Image generation defaults.
// Default provider is Runware; OpenRouter (Gemini) remains as the alternate.
// Default model is FLUX.1 schnell via Runware (~$0.0006/image, ~2s). CEO
// decision 2026-04-21 after comic A/B: schnell's looser fidelity reads as
// editorial-cartoon style and is 65× cheaper than Nano Banana. Users can
// override via Settings → Images → Image Model.
define( 'PRAUTOBLOGGER_DEFAULT_IMAGE_PROVIDER', 'runware' );
define( 'PRAUTOBLOGGER_DEFAULT_IMAGE_MODEL', 'runware:100@1' );
// Default style suffix appended to every image-gen prompt. Updated
// 2026-04-18 v4: single-panel newspaper comic style. Caption is now
// inserted as HTML text below the image, NOT rendered inside it.
// Users may override via Settings → Images → Style Suffix.
define(
'PRAUTOBLOGGER_DEFAULT_IMAGE_STYLE_SUFFIX',
'Style: a single-panel newspaper comic in classic black ink on white paper, simple clean linework like The Far Side or B.C., slightly yellowed newsprint texture, hand-drawn crosshatching for shading, bold panel border, warm and humorous tone, visible in a newspaper comics section. IMPORTANT: Do not render any text, captions, speech bubbles, or words inside the image.'
);
// Default editorial-illustration prompt template for the image generator.
// Updated 2026-05-29 (v0.16.0): pivot from single-panel comic to text-free
// editorial scientific illustration. The {{ topic_summary }} token is filled
// at build time with a 1-2 sentence topic/mechanism description from the
// rewriter LLM; the rest is brand-locked. Replaces the old Style Suffix.
// Users may override via Settings -> Images -> Style Template. Per brief A5
// the template must contain exactly one {{ topic_summary }} token.
define(
'PRAUTOBLOGGER_DEFAULT_IMAGE_STYLE_TEMPLATE',
"Editorial scientific illustration, flat vector aesthetic, limited palette of\n" .
"teal (#1B8A92) and cream (#FAF6EE) with ink (#0D0D0D) line work and accents\n" .
"of orange (#FF8A3D) and lime (#7FD600). Subject: {{ topic_summary }}.\n" .
"Style reference: Quanta Magazine, The Pudding, scientific editorial illustration.\n" .
"Composition: clear focal point, balanced negative space, subtle textural detail.\n" .
'Mood: authoritative, clinical, calm. NO TEXT. NO PEOPLE. NO LOGOS. NO LABELS.'
);
// Opik observability (feature-flag gated).
// API credentials must be defined in wp-config.php constants:
// - PRAUTOBLOGGER_OPIK_API_KEY: API key for authentication
// - PRAUTOBLOGGER_OPIK_WORKSPACE: Opik workspace name (e.g. 'peptiderepo')
// - PRAUTOBLOGGER_OPIK_URL_OVERRIDE: Optional; defaults to https://www.comet.com/opik/api/
if ( ! defined( 'PRAUTOBLOGGER_OPIK_API_KEY' ) ) {
define( 'PRAUTOBLOGGER_OPIK_API_KEY', '' );
}
if ( ! defined( 'PRAUTOBLOGGER_OPIK_WORKSPACE' ) ) {
define( 'PRAUTOBLOGGER_OPIK_WORKSPACE', 'peptiderepo' );
}
if ( ! defined( 'PRAUTOBLOGGER_OPIK_URL_OVERRIDE' ) ) {
define( 'PRAUTOBLOGGER_OPIK_URL_OVERRIDE', 'https://www.comet.com/opik/api/' );
}
/*
|--------------------------------------------------------------------------
| Autoloader
|--------------------------------------------------------------------------
*/
require_once PRAUTOBLOGGER_PLUGIN_DIR . 'includes/class-autoloader.php';
PRAutoBlogger_Autoloader::register();
// The main orchestrator class is loaded explicitly because its CamelCase name
// (PRAutoBlogger) doesn't map cleanly to a filename via the autoloader's
// kebab-case convention (it would look for class-pr-auto-blogger.php).
require_once PRAUTOBLOGGER_PLUGIN_DIR . 'includes/class-prautoblogger.php';
/*
|--------------------------------------------------------------------------
| Activation & Deactivation
|--------------------------------------------------------------------------
*/
register_activation_hook( __FILE__, array( 'PRAutoBlogger_Activator', 'activate' ) );
register_deactivation_hook( __FILE__, array( 'PRAutoBlogger_Deactivator', 'deactivate' ) );
/*
|--------------------------------------------------------------------------
| Boot the Plugin
|--------------------------------------------------------------------------
| The main orchestrator registers all hooks. Nothing else happens here —
| keep the bootstrap file minimal.
*/
/**
* Returns the singleton instance of the main plugin class.
*
* @return PRAutoBlogger
*/
function prautoblogger(): PRAutoBlogger {
static $instance = null;
if ( null === $instance ) {
$instance = new PRAutoBlogger();
}
return $instance;
}
// Initialize on plugins_loaded so all dependencies are available.
add_action(
'plugins_loaded',
static function (): void {
prautoblogger()->run();
}
);