Files
prautoblogger/CHANGELOG.md
peptiderepo 5c7736bf4e v0.8.0: fix silent image failures + NSFW retry + prompt instructions (#97)
* fix(image-pipeline): surface per-image failures in the event log

Two gaps made posts 650/657 appear to silently "lose" their featured
images on 2026-04-20:

1. The batch provider tagged each failed key with ['error' => ...] but
   the ERROR log line it emitted didn't identify which key failed, so
   operators couldn't tell image_a from image_b in the event log when
   one of two parallel requests died.

2. Image_Pipeline::process_image_a/_b folded the per-key error into
   $result['errors'] and returned silently, with no WARNING row. Ops
   only learned about the miss when the outer catch in the caller fired
   — and on the 2026-04-20 incident that outer catch didn't fire, so
   nothing was logged at all except an unrelated pricing warning.

Fix:

- class-open-router-image-batch.php: collect_result() takes the request
  key as its first argument and every Logger::error message reads
  'OpenRouter batch image gen <class> for key "<key>": <detail>'.
  Matches the shape of OpenRouter_Image_Support::log_retry.

- class-image-pipeline.php: process_image_a and process_image_b now
  emit a Logger::warning('image_pipeline') row whenever $image_data is
  missing or carries an error key, in addition to pushing to
  $result['errors']. The warning fires whether or not the outer catch
  runs.

Test: ImagePipelineTest::test_partial_batch_failure_emits_warning_log_row
mocks $wpdb, runs the pipeline with image_a erroring, and asserts at
least one warning/image_pipeline row was inserted.

No behavior change beyond logging; cost impact zero.

Part of the v0.8.0 image bundle (issues from thread image-mime-bug).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(images): retry Cloudflare NSFW-blocked prompts with a sanitized fallback

Cloudflare's Workers AI FLUX.1 schnell content filter sometimes rejects
peptide-comic prompts as NSFW (HTTP 400, errors[].code === 3030). On
2026-04-20 this produced a silent publish-without-featured-image for
post #650 ("Semaglutide vs Tirzepatide") — the LLM-rewritten scene
tripped the filter and we had no recovery path.

New behaviour: on a 3030 response, the Cloudflare provider throws a
typed PRAutoBlogger_Image_NSFW_Blocked exception. The batch wrapper
catches it and tags the per-slot result with
`error_type: nsfw_blocked`. Image_Pipeline hands the results to a new
PRAutoBlogger_Image_NSFW_Retry helper, which reruns the blocked slot
once with a sanitized fallback prompt (title + style suffix, no LLM
rewrite). On success the retry result replaces the error entry; on a
second block, a WARNING is logged and the pipeline publishes without
that image — same terminal behaviour we had before, just explicit.

Files:

- New providers/class-image-nsfw-blocked.php — typed exception.
- providers/class-cloudflare-image-support.php — is_nsfw_error() +
  throw_if_nsfw() helpers.
- providers/class-cloudflare-image-provider.php — calls throw_if_nsfw
  inside the 4xx branch; batch catches the typed exception and marks
  the result with error_type.
- core/class-image-prompt-builder.php — new public build_fallback_prompt
  shim so the retry layer can reach the rule-based fallback without
  going through the LLM path.
- New core/class-image-nsfw-retry.php — single-shot retry helper.
- core/class-image-pipeline.php — invokes the retry between batch
  completion and per-slot processing.
- admin/class-settings-fields-extended.php — new toggle
  `prautoblogger_image_nsfw_retry` (default on) in the Images section.

Tests:

- CloudflareImageProviderTest — 3030 response throws NSFW exception;
  batch tags the per-slot result with `error_type: nsfw_blocked`.
- ImagePipelineTest — retry succeeds when provider returns a good
  result on the second call; retry is skipped when the setting is off.

Cost impact: worst-case an extra Cloudflare call per NSFW-blocked slot
(~$0.000834). Current incidence ~2% of generations; ~+$0.00002 per
generation amortized. Immaterial.

Uninstall: new option covered by the existing prautoblogger_% LIKE
sweep in uninstall.php.

Part of the v0.8.0 image bundle (issues from thread image-mime-bug).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(images): expose rewriter system prompt as admin setting

CEO feedback: every generated image is anchoring on the cartoon
peptide-with-arms-and-legs character because the hardcoded rewriter
system prompt both lists "anthropomorphized molecules" first in its
primer and ships a few-shot example that literally shows one. The ask
is to give CEO the lever without a code change.

Adds `prautoblogger_image_prompt_instructions` (textarea) in the
Images section. Default value is the existing REWRITER_SYSTEM_PROMPT
constant verbatim, so behaviour is unchanged on upgrade until someone
edits it. `Image_Prompt_Builder::rewrite_via_llm()` reads the option
at call time via a new `resolve_system_prompt()` helper and falls back
to the constant if the option is blank or whitespace-only, so a saved
empty value cannot brick image generation.

Const visibility flipped from `private` to `public` so the settings
field can reference it directly as the field default.

Tests:

- `test_rewrite_uses_setting_when_present` — option set, assert the
  LLM's system message equals the option content.
- `test_rewrite_falls_back_to_default_when_setting_empty` — option
  whitespace, assert the LLM's system message equals the constant.

Uninstall: new option covered by the existing prautoblogger_% LIKE
sweep in uninstall.php.

Cost impact: none by default. Future CEO edits will change prompt
length; current default is ~180 tokens, budget impact linear from
there.

Part of the v0.8.0 image bundle (thread image-mime-bug, message 03).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(images): collapse Image Provider + Image Model into a single dropdown

Until now, Image Provider and Image Model were two independent selects
in the admin UI. That let the CEO set Model=flux-1-schnell while
Provider was still openrouter — OpenRouter has no such model so every
image gen on posts 650/657 on 2026-04-20 returned HTTP 400, and the
article published without a featured image.

v0.8.0 removes the Image Provider field from the admin UI entirely.
The provider is now derived from the model's entry in the registry
every time the model is saved. A new
PRAutoBlogger_Image_Model_Registry::provider_for() helper centralises
the mapping, so admin save, the on-upgrade migration, and future
callers all read from one place.

On save, PRAutoBlogger_Admin_Page::sanitize_image_model() validates
the submitted slug against the registry:
- Known slug: persist it, and update `prautoblogger_image_provider` to
  the paired provider as a side-effect.
- Unknown slug: add_settings_error with a clear message, and return
  the previously-saved value so the site stays on a working model.

A one-shot v0.8.0 migration in
PRAutoBlogger::on_check_db_version() derives the provider from the
currently-saved model on first admin_init after the plugin is updated.
This auto-heals sites that were running mismatched pairs before the
upgrade, without requiring the admin to re-open settings.

`prautoblogger_image_provider` option key is preserved — all existing
reads in the pipeline, cost tracker, and validators continue to work
unchanged.

Tests (Services/ImageModelRegistryTest):

- Every registry entry has an id and a provider in {openrouter, cloudflare}.
- provider_for() returns the paired provider for a known slug.
- provider_for() returns '' for unknown/empty slugs.

Cost impact: none.

Migration idempotent via the `prautoblogger_migrated_image_provider_v080`
flag. Uninstall sweeps it along with every other plugin option.

Part of the v0.8.0 image bundle (thread image-mime-bug, Issue 1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(v0.8.0): version bump + CHANGELOG + ARCHITECTURE.md for image bundle

Bump plugin header and PRAUTOBLOGGER_VERSION 0.7.3 → 0.8.0. Minor bump
because Commit D removes the Image Provider dropdown from admin (a
UX-visible change), not a patch.

CHANGELOG:
- Changed: collapsed Image Provider + Model dropdowns; provider derived.
- Added: Image Prompt Instructions setting; Retry NSFW-Blocked Images
  setting.
- Fixed: silent per-image failures in the event log.

ARCHITECTURE.md:
- Stage 6b (Image Pipeline) callout now describes provider derivation
  and the NSFW-retry fallback path.
- wp_options table lists the four new/changed keys
  (`prautoblogger_image_provider` as derived, new
  `prautoblogger_image_prompt_instructions`,
  `prautoblogger_image_nsfw_retry`, and the one-shot migration flag
  `prautoblogger_migrated_image_provider_v080`).

DB schema unchanged — no PRAUTOBLOGGER_DB_VERSION bump needed.

Final commit of the v0.8.0 image bundle (thread image-mime-bug).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: peptiderepo <peptiderepo@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 02:02:20 +07:00

25 KiB
Executable File
Raw Blame History

Changelog

All notable changes to PRAutoBlogger will be documented in this file.

Format follows Keep a Changelog, and this project uses Semantic Versioning.

[0.8.0] — 2026-04-21

Changed

  • Single Image Model dropdown in admin → Images. The separate Image Provider select has been removed. The provider (OpenRouter or Cloudflare Workers AI) is now derived from the chosen model's entry in PRAutoBlogger_Image_Model_Registry on save, so mismatched provider/model pairs — the root cause of posts 650 and 657 silently missing their featured image on 2026-04-20 — are no longer possible. A one-shot migration runs on first admin_init after upgrade and auto-heals any existing site where the saved provider and model disagreed. The prautoblogger_image_provider option key is preserved; all existing reads in the pipeline continue to work.

Added

  • Image Prompt Instructions setting. The system prompt used to rewrite articles into SCENE + CAPTION is now editable from admin → Images. Default preserves prior behavior; edit to reshape the creative direction without a code change.
  • Retry NSFW-Blocked Images setting (default on). When the image provider rejects a prompt as NSFW — Cloudflare Workers AI returns HTTP 400 with errors[].code === 3030 on FLUX.1 schnell — PRAutoBlogger now retries the slot once with a rule-based fallback prompt (article title + style suffix, no LLM rewrite). On a second block it logs a WARNING and publishes without that image, matching existing behaviour for any unrecoverable image failure.

Fixed

  • Silent per-image failures in the event log. The OpenRouter batch provider now emits Logger::error with the request key for every per-slot HTTP/cURL/parse failure (previously logged the error but not which slot it was), and Image_Pipeline::process_image_a/_b emit Logger::warning on ['error' => …] results whether or not the outer catch fires. Operators can now see NSFW blocks, 4xx bodies, and timeouts in the admin Event Log without cross- referencing PHP error_log timestamps.

[0.7.3] — 2026-04-20

Fixed

  • Table colors broken in dark mode. Replaced hardcoded hex colors with theme CSS custom properties (--color-border-default, --color-bg-secondary, --color-text-primary) so table borders, header backgrounds, and text adapt to light/dark mode automatically.

[0.7.2] — 2026-04-20

Fixed

  • Bullet points missing in articles. Theme's global CSS reset strips list-style from all ul/ol. Typography CSS now restores disc/decimal bullets with proper padding inside .entry-content.
  • Tables break on mobile. Tables now wrapped in a horizontally scrollable container (prab-table-wrap) via the_content filter. Added responsive media query for tighter padding and smaller font at narrow viewports.

[0.7.1] — 2026-04-20

Fixed

  • Typography CSS not applying. Two bugs: CSS targeted .post-content but the theme template uses .entry-content, and the _prautoblogger_generated meta gate excluded posts that predate the meta flag. Display settings now apply to all single post pages and target the correct .entry-content selector.

[0.7.0] — 2026-04-20

Added

  • Display settings section. New "Display" tab in admin with three settings that control how generated articles render on the frontend:
    • Article Font — choose from 7 font families (Inter, Georgia, Merriweather, Lora, Open Sans, Roboto, System) or keep theme default. Google Fonts are loaded only on PRAutoBlogger posts that need them.
    • Article Font Size — set body text size in pixels (0 = theme default). Recommended 16–18px for comfortable reading.
    • Table Borders — adds visible borders, cell padding, header background, and alternating row colors to tables. Enabled by default.
  • Article_Typography frontend class. Injects inline CSS only on singular post pages with _prautoblogger_generated meta — zero impact on non-PRAutoBlogger pages.

[0.6.3] — 2026-04-20

Fixed

  • Unchecking all sources still used Reddit. Browser omits checkbox fields from POST when none are checked, so the prautoblogger_enabled_sources option never updated. Added a hidden input so an empty value is always submitted — unchecking all sources now correctly saves [].
  • Pipeline status hardcoded "Reddit". The "Collecting sources…" stage message now reflects the actually-enabled sources instead of always saying "Reddit".

[0.6.2] — 2026-04-20

Added

  • Deterministic peptide auto-linker. New Peptide_Linker class scans generated HTML and wraps every mention of a known peptide in a hyperlink to its /peptides/{slug}/ database page. Runs as a post-processing step in Publisher before wp_insert_post — no LLM involvement, 100% reliable. Handles case-insensitive matching, hyphen/space variants (e.g., "BPC-157" and "BPC 157"), and skips text already inside <a> tags. Gracefully no-ops if PR Core is not active.

Changed

  • Removed prompt-based peptide linking. The system prompt no longer asks the LLM to link peptides (unreliable). Peptide links are now injected deterministically by Peptide_Linker after content generation. The prompt still provides article links and the "never fabricate URLs" rule.

[0.6.1] — 2026-04-20

Added

  • Peptide auto-linking. When any peptide name from the PR Core database is mentioned in a generated article, the first mention is automatically linked to the corresponding /peptides/{slug}/ page. The system prompt now includes a complete peptide database reference list alongside the existing article links. Gracefully degrades if PR Core is not active (post_type_exists() guard).

Changed

  • Extracted prompt builders into Content_Prompts class. Moved all prompt construction (system prompt, stage prompts, linking rules) from Content_Generator into a new Content_Prompts static helper. Generator dropped from 324 → 162 lines; prompts class is 252 lines. Both under 300.
  • Internal link rules strengthened. Linking rules section now explicitly instructs the model to link every peptide's first mention and never fabricate URLs, with real published article and peptide page URLs provided.

[0.6.0] — 2026-04-20

Added

  • Generate article from idea. New "Generate" column in the Ideas browser. Click any idea's Generate button to produce an article directly, bypassing the scheduled pipeline's collect → analyze → score steps. The button shows real-time stage updates (drafting, editing, publishing) via per-idea status polling, then swaps to Edit/View links with cost on completion.
    • Ideas_Browser::on_ajax_generate_from_idea() — AJAX trigger that stores the idea and schedules a one-shot WP-Cron event.
    • Ideas_Browser::on_cron_generate_from_idea() — Background handler that runs Article_Worker for the selected idea.
    • Ideas_Browser::on_ajax_idea_gen_status() — Per-idea status polling.
    • assets/js/ideas-browser.js — Button click, AJAX, polling, and UI state.
    • Per-idea transients (prab_idea_gen_{id}) for independent status tracking.
    • Page-load polling resume for ideas that were mid-generation.
    • Generation lock respected — only one generation at a time.

[0.5.3] — 2026-04-20

Fixed

  • Writing instructions not obeyed by LLM. The user-configured writing instructions (bullet points, hyperlinks, style rules) were appended to the system prompt as a weak "Additional instructions:" afterthought. Models prioritized competing format directives in the user prompts and ignored the style guide.
    • System prompt now frames writing instructions as a "MANDATORY STYLE GUIDE" with explicit override language.
    • Every stage's user prompt (outline, draft, single-pass) now includes a reinforcement line: "Follow EVERY requirement from your system prompt style guide."
    • Polish stage explicitly preserves bullet points, numbered lists, and hyperlinks instead of flattening them.
    • Chief editor's revision rules now preserve structural formatting (lists, links) from the original draft.

[0.5.2] — 2026-04-20

Added

  • LLM research cost amortization. The research LLM call (which runs once per pipeline execution) is now divided evenly across all articles produced in that run. Each article's cost breakdown popover shows its amortized share of the research overhead. Previously, research costs were orphaned with no post attribution.
    • Post_Assembler::amortize_research_costs() — Queries the unlinked research log entry, divides cost and tokens by article count, inserts one row per article, and removes the original.
    • link_generation_logs() now excludes llm_research stage from per-article linking so the cost isn't grabbed wholesale by the first article.
    • Source_Collector::set_cost_tracker() — Accepts the pipeline's cost tracker so research costs are tagged with the pipeline's run_id.
    • LLM_Research_Provider now uses the pipeline's cost tracker when available, ensuring research log entries share the same run_id as article entries.
    • Amortization runs after all articles complete (both single-article and chained-job paths in Pipeline_Runner).

[0.5.1] — 2026-04-20

Added

  • Ideas browser admin page. New "Ideas" submenu under PRAutoBlogger showing all article ideas collected by the content analyzer. Displays suggested title, topic, type (question/complaint/comparison/news/guide), relevance score bar, frequency, key points, and target keywords. Filterable by type with count badges. Paginated at 30 per page, newest first.

[0.5.0] — 2026-04-20

Added

  • LLM Deep Research source provider. New data source that sends a configurable research prompt to a reasoning-capable model (e.g. Grok 4.1 Fast, DeepSeek-R1) to identify trending topics, emerging questions, misconceptions, and content gaps. Findings feed into the content analyzer alongside Reddit data, giving the pipeline both real-time community signals AND deep knowledge-base research.
    • includes/providers/class-llm-research-provider.php — Source provider implementation.
    • New admin settings in Sources tab:
      • "LLM Deep Research" checkbox in Enabled Sources.
      • "Research Model" model picker — pick a reasoning-capable model.
      • "Research Prompt" textarea — customizable research brief with {niche} placeholder.
    • Each research run produces 8-12 findings stored as llm_research source data.
    • Deduplication via date-based source IDs (one fresh run per day, refreshes on re-run).
    • Cost tracked as llm_research stage in generation logs.
  • Content analyzer now formats LLM research findings with a cleaner label ([LLM Research] Relevance: N) instead of the Reddit-specific r/subreddit format.

[0.4.9] — 2026-04-19

Added

  • OpenRouter reasoning mode support. New "Enable Reasoning" toggle and "Reasoning Effort" selector in AI Models settings. When enabled, sends reasoning: {enabled: true, effort: "<level>"} to models that support it (Grok 4.1 Fast, DeepSeek-R1, etc.). Effort levels: Extra High, High, Medium, Low, Minimal. Reasoning tokens are billed as output tokens — cost is automatically tracked. Models that don't support reasoning ignore the parameter. Per-call override available via the reasoning key in the provider options array.
  • Response parser now captures reasoning_tokens and reasoning_content from the OpenRouter response for downstream logging and debugging.

[0.4.8] — 2026-04-19

Fixed

  • GLM 4.7 Flash timeout during analysis. The model was timing out at 120s on large analysis prompts (especially with the new custom instructions). Bumped API timeout from 120s to 180s.
  • Empty raw response in error logs. Parse failure errors now include the raw LLM response inline (first 500 chars) at error level, not debug level, so the actual failure reason is always visible in the Activity Log.

[0.4.7] — 2026-04-19

Added

  • Cost breakdown popover. The Cost column in the post list is now clickable. Hovering or clicking shows a per-step breakdown: stage name, model used, token count, and cost for every LLM call that produced the article.

[0.4.6] — 2026-04-19

Added

  • Image B toggle. New "Generate Second Image (B)" toggle in Images settings. Disabling saves one image generation call + one LLM prompt rewrite per article. Defaults to enabled for backwards compatibility.

[0.4.5] — 2026-04-19

Added

  • Analysis Instructions setting. Custom instructions appended to the analysis LLM's system prompt. Steers how source data is evaluated and which topic ideas get surfaced.
  • Editor Instructions setting. Custom instructions appended to the chief editor's system prompt. Controls what the editorial review looks for and how it decides to approve, revise, or reject articles.

[0.4.4] — 2026-04-19

Added

  • Writing Instructions setting. New textarea in Content settings lets you provide custom instructions appended to the LLM's system prompt when writing articles. Use it to steer style, structure, voice, and formatting without editing code.

[0.4.3] — 2026-04-19

Fixed

  • "Analysis response was not valid JSON" with some models. Models like GPT 5.1 Nano ignore response_format: json_object and wrap JSON in markdown fences or preamble text. New PRAutoBlogger_Json_Extractor utility strips fences and extracts the outermost JSON object. Applied to both the content analyzer and chief editor parsers. Raw response now logged on failure for debugging.

[0.4.2] — 2026-04-19

Fixed

  • Posts list Title column unreadable. Title was compressed to ~30px by the three custom columns. Title now gets 35% minimum width with word-wrap enabled. Model columns trimmed to 100px with text-overflow ellipsis for long model names.

[0.4.1] — 2026-04-19

Added

  • Posts list columns: Writing Model, Image Model, Cost. Three new columns on the WordPress Posts admin page. Writing Model shows the LLM that wrote the article, Image Model shows the model that generated the featured image, and Cost shows the total API spend for generating that article (summed from the generation log). All columns show "—" for non-generated posts. Writing Model is sortable. Model names are shortened (provider prefix stripped) with full ID in a tooltip.

[0.4.0] — 2026-04-19

Added

  • Semantic dedup via embedding cosine similarity. Replaces keyword-overlap dedup (60% word match) with MiniLM-L12-v2 embeddings via OpenRouter. Catches rephrasings like "BPC-157 dosing" vs "how much BPC-157 to take" that keyword matching misses. Automatic fallback to keywords if embedding API unavailable. Cost: ~$0.00001 per generation run.

    • includes/core/class-semantic-dedup.php — Dedup engine with embedding + keyword fallback.
    • includes/providers/class-open-router-embedding-provider.php — OpenRouter /embeddings client with batch support and cosine similarity helper.
  • LLM-aware topic avoidance. Analysis prompt now includes the last 30 days of published article titles with explicit "do not suggest similar topics" instruction. Zero additional cost — appended to the existing analysis call.

Changed

  • Dedup window widened from 7 days to 30 days (semantic similarity is precise enough to avoid over-blocking).
  • class-idea-scorer.php refactored to delegate dedup to Semantic_Dedup.

Fixed

  • Featured images displayed at 300×300 (WordPress "medium" size) instead of full width. Changed theme to use the_post_thumbnail('full') with responsive CSS.
  • Race condition causing duplicate article generation when the AJAX status poller re-scheduled a cron event for an article already being generated.
  • False stall detection on multi-article runs (measured elapsed time from start instead of idle time since last progress update).

[Unreleased]

Added

  • Image pipeline integration: wires image generation into article flow (commit 1b). Completes the image + Instagram A/B experiment workstream by adding three orchestration classes that generate and sideload two images per published post.

    • includes/core/class-image-pipeline.php — Orchestrates A/B image generation: generates Image A (article-driven prompt) as featured image and Image B (source-driven prompt) stored in post meta _prautoblogger_image_b_id. Each image is independently fallible; article publishes even if both fail.
    • includes/core/class-image-prompt-builder.php — Synthesizes visual prompts from article title + first paragraph (for Image A) and Reddit thread title + top comment (for Image B). Both prompts append the CEO-locked 90s infomercial style suffix from settings. Prompts kept concise (under 200 words) for FLUX.1 generation quality.
    • includes/core/class-image-media-sideloader.php — Downloads generated image bytes, creates temporary file, imports via media_handle_sideload(), sets alt text and generation metadata (_prautoblogger_image_*), cleans up temp files.
    • Settings: prautoblogger_image_enabled (toggle, default off) — allows users to enable/disable image generation after providing Cloudflare credentials.
    • PRAutoBlogger_Publisher::attach_generated_images() — New private method that runs the image pipeline post-creation and sets _thumbnail_id (Image A) and _prautoblogger_image_b_id (Image B) post meta. Errors are logged but do not block post publication.
    • Tests: tests/unit/Core/ImagePipelineTest.php (orchestration, cost tracking, graceful failure modes), tests/unit/Core/ImagePromptBuilderTest.php (prompt generation from various article/source shapes, length limits).
  • ARCHITECTURE.md: added new image pipeline step (6b) to data flow, three new files to core/ section of file tree.

  • OpenRouter model registry (model-picker commit 1). Foundation for the smart model picker: fetches, normalizes, and caches the OpenRouter model list (/api/v1/models — free, unauthenticated). Daily refresh via the existing prautoblogger_daily_generation cron hook with 12h idempotency.

    • includes/services/interface-model-registry.php — Phase 3-aware contract.
    • includes/services/class-open-router-model-registry.php — fetch + cache + query.
    • includes/services/class-open-router-model-normalizer.php — raw → standardized shape.
    • Capability vocabulary: text→text, text+image→text, text→embedding, etc.
    • Zero-coupling: no PRAUTOBLOGGER_* constants inside the class — Phase 2 lift into a shared Composer package requires only a namespace rename.
    • Cost impact: $0/month (free endpoint, no LLM tokens).
  • Image provider: Cloudflare Workers AI (FLUX.1 family). Ships the provider, its pricing + validator helpers, four new settings fields in a new "Images" admin section, and unit tests with mocked HTTP.

    • includes/providers/interface-image-provider.php — contract for any image provider.
    • includes/providers/class-cloudflare-image-provider.php — FLUX on Workers AI, direct call to /accounts/{id}/ai/run/... (bypassing AI Gateway per decision D-001); exponential-backoff retries on 429 / 5xx / network, loud fail on 4xx; handles both raw-bytes and JSON-envelope response shapes.
    • includes/providers/class-cloudflare-image-pricing.php — model alias → full Workers AI id resolution + per-megapixel cost estimation.
    • includes/providers/class-cloudflare-image-validator.php — non-destructive "Test Connection" credential check that never generates a real image.
    • Settings: prautoblogger_cloudflare_ai_token (encrypted), prautoblogger_cloudflare_account_id, prautoblogger_image_model (schnell / dev), prautoblogger_image_style_suffix (default = CEO-locked 90s infomercial prompt).
    • Constants: PRAUTOBLOGGER_DEFAULT_IMAGE_MODEL, PRAUTOBLOGGER_DEFAULT_IMAGE_STYLE_SUFFIX.
    • Tests: tests/unit/Providers/CloudflareImageProviderTest.php.
  • ARCHITECTURE.md: new key decision #16 (image pipeline: Cloudflare Workers AI), new external API integration row, new options rows, file tree updates.

  • CONVENTIONS.md: new "How To: Add a New Image Provider" section.

[0.2.2] — 2026-04-12

Changed

  • CI/CD pipeline now runs PHPCS and PHPUnit before deploying. Added shivammathur/setup-php@v2 for PHP 8.1, Composer install, WordPress Coding Standards check, and full PHPUnit test suite. PHPCS runs in report-only mode (|| true) while codebase is brought into full compliance; PHPUnit failures block deploy.
  • Removed stub source providers (TikTok, Instagram, YouTube). These were dead code — not registered in Source_Collector, threw RuntimeException if enabled, and confused AI agents reading the codebase. The Source_Provider_Interface remains the contract for adding future platforms (see CONVENTIONS.md).
  • Removed "coming soon" checkboxes from Enabled Sources setting.
  • Updated ARCHITECTURE.md: removed stub file references, added cross-system LLM budget coordination section documenting how PRAutoBlogger and Peptide News share an OpenRouter account.

Improved

  • PublisherTest rewritten with real behavioral assertions. Now tests: post_status ('publish' vs 'draft'), generation metadata storage, title from idea, taxonomy assignment, tag assignment, run_id-based log linking, RuntimeException on WP_Error, action/filter hook firing. Replaces previous method-existence-only checks.

[0.2.1] — 2026-04-12

Changed

  • Reddit data source switched to RSS primary, .json fallback. PullPush.io was frequently stale/unavailable. Reddit RSS/Atom feeds work reliably from datacenter IPs (Hostinger) where .json gets 403. Comments are still fetched via .json (unavailable in RSS).
  • Updated "Enabled Sources" checkbox label to "Reddit (RSS + .json)".
  • Updated "Test Connections" to show RSS + .json status.
  • Source status indicator now shows Reddit RSS (Primary) and .json (Fallback).

Removed

  • class-pull-push-client.php — replaced by RSS feeds in class-reddit-json-client.php.
  • All PullPush.io references from admin UI, docs, and provider code.

Fixed

  • Generate Now button now always sends force: '1' to clear stale generation locks.
  • Fixed encryption double-encryption bug with enc: prefix detection.

[0.2.0] — 2026-04-12

Changed

  • Reddit data source migrated from OAuth to RSS + .json (no auth required). Reddit rejected our API application; RSS feeds and .json endpoints are free and require no authentication.
  • Removed Reddit Client ID and Client Secret fields from admin API Keys tab.
  • Updated "Test Connections" results to show Reddit source status.

Added

  • Source status indicator in API Keys tab showing RSS and .json availability with live status dots and last-collection timestamp.
  • Configurable research cache TTL (1–72 hours) in Sources tab.
  • Reddit time window selector (24h / week / month) in Sources tab.
  • Posts-per-subreddit limit (5–100) in Sources tab.
  • LiteSpeed cache purge step in CI/CD deploy pipeline.

Removed

  • class-reddit-api-client.php — replaced by class-reddit-json-client.php.
  • Reddit OAuth credential fields and encrypted storage entry.

Fixed

  • Deploy pipeline now purges LiteSpeed/OPcache after file extraction, preventing stale PHP bytecode from being served.

[0.1.0] — 2026-04-10

Added

  • Initial plugin scaffold with WordPress-native architecture.
  • OpenRouter LLM provider with configurable model selection and retry logic.
  • Reddit API source provider (OAuth2 script app) for collecting posts and comments.
  • Content analysis engine: detects recurring questions, complaints, and comparisons.
  • Idea scorer with deduplication against existing posts.
  • Configurable writer agent pipeline (single-pass or multi-step: outline → draft → polish).
  • Chief editor agent for LLM-powered editorial review before publishing.
  • Publisher with full generation metadata stored as post_meta.
  • Cost tracker with per-call logging, monthly budget enforcement, and hard-stop.
  • Metrics collector: WordPress native + GA4 integration + composite content scoring.
  • Admin settings page with sections for API keys, models, content, sources, schedule, and analytics.
  • Metrics dashboard showing monthly spend, budget utilization, cost by stage, and daily spend.
  • Post metabox showing generation metadata on PRAutoBlogger-generated posts.
  • Admin notices for missing API keys, budget warnings, and configuration hints.
  • Stub providers for TikTok, Instagram, and YouTube (future implementation).
  • Encrypted storage for API keys using AES-256-CBC with wp_salt().
  • AJAX-powered "Generate Now" and "Test Connections" buttons.
  • Custom database tables for source data, analysis results, generation logs, and content scores.
  • Clean uninstall handler removing all plugin data.
  • ARCHITECTURE.md and CONVENTIONS.md for AI-readable codebase documentation.