* ci: add main-push audit tripwire Flags direct pushes to main that did not come from a merged PR. Free-plan workaround since branch protection requires GitHub Pro. * docs: add Git Workflow section to CONVENTIONS.md Documents the PR-gated review process enforced at the agent layer. Free-plan workaround since branch protection requires GitHub Pro. * ci: add Claude-powered CTO PR review workflow Auto-reviews every PR with Claude Haiku 4.5 and posts a structured review (APPROVE / REQUEST_CHANGES / COMMENT). Does NOT auto-merge — a human still clicks Merge. Requires ANTHROPIC_API_KEY secret on the repo. * ci: switch CTO reviewer from Anthropic direct to OpenRouter Reuses the OPENROUTER_API_KEY secret that already backs the other AI plugins. Consolidates AI spend on one bill. Model: anthropic/claude-haiku-4.5.
12 KiB
Executable File
PRAutoBlogger — Conventions
This document codifies the naming patterns, coding conventions, and step-by-step guides for extending PRAutoBlogger. Update this file whenever a new pattern is introduced.
Naming Conventions
PHP Classes
- Format:
PRAutoBlogger_{PascalCase}(e.g.,PRAutoBlogger_Content_Generator) - File naming:
class-{kebab-case}.php(e.g.,class-content-generator.php) - Interfaces:
interface-{kebab-case}.phpwithPRAutoBlogger_{Name}_Interfaceclass name
Hooks (Actions & Filters)
- Prefix: All hooks start with
prautoblogger_ - Actions:
prautoblogger_{event}(e.g.,prautoblogger_before_content_generation) - Filters:
prautoblogger_filter_{what}(e.g.,prautoblogger_filter_article_idea_score) - Hook callbacks registered in:
class-prautoblogger.php(central loader), never in the classes themselves
Database
- Table prefix:
{$wpdb->prefix}prautoblogger_(e.g.,wp_prautoblogger_source_data) - Option prefix:
prautoblogger_(e.g.,prautoblogger_openrouter_api_key) - Post meta prefix:
_prautoblogger_(underscore prefix hides from custom fields UI) - Transient prefix:
prautoblogger_(e.g.,prautoblogger_reddit_token)
Constants
- Format:
PRAUTOBLOGGER_{UPPER_SNAKE}(e.g.,PRAUTOBLOGGER_MAX_RETRIES) - Defined in:
prautoblogger.php(bootstrap file) for global constants
Hook Callback Methods
- Action callbacks: Prefixed with
on_(e.g.,on_daily_generation_triggered) - Filter callbacks: Prefixed with
filter_(e.g.,filter_content_quality)
Error Handling & Logging
Structured Logger
All logging uses PRAutoBlogger_Logger (singleton) instead of raw error_log(). The logger writes to the prab_event_log custom table and forwards errors/warnings to PHP's error_log() for server-level monitoring. The verbosity threshold is user-configurable via Settings → Publishing → Log Level.
Never call error_log() directly. Always use the Logger:
$logger = PRAutoBlogger_Logger::instance();
$logger->error( 'API call failed: ' . $error_message, 'openrouter' );
$logger->warning( 'Budget nearing limit.', 'cost-tracker' );
$logger->info( 'Pipeline complete: 3 posts generated.', 'pipeline' );
$logger->debug( 'Token counts: prompt=1200, completion=800', 'openrouter' );
Log levels (ascending verbosity)
| Level | Numeric | Use for |
|---|---|---|
| error | 0 | Failures: API errors, parse failures, DB write failures |
| warning | 1 | Degraded behavior: budget approaching, missing providers, fallback pricing |
| info | 2 | Key events: pipeline start/stop, articles generated, metrics collected |
| debug | 3 | Verbose detail: token counts, timing, skipped posts, intermediate values |
Context strings
The second argument to every log call is a short context tag identifying the origin:
pipeline, scheduler, collector, analyzer, editor, openrouter, reddit, ga4, metrics, cost-tracker, encryption.
What gets shown to the user
- API key issues → admin notice (persistent until resolved)
- Budget exceeded → admin notice + email to site admin
- Generation failures → logged to
prab_generation_logwithresponse_status = 'error' - Generation failures also shown in the admin dashboard widget
- All log entries visible in PRAutoBlogger → Activity Log (filterable by level)
What gets retried
- LLM API calls: exponential backoff, max 3 retries, then fail loudly
- Reddit API calls: exponential backoff, max 3 retries, then skip this collection cycle
- Never retry silently. Every retry is logged. Every final failure is surfaced.
Error severity levels
- Fatal: Plugin cannot function (missing API key, budget exhausted) → admin notice, stop all scheduled jobs
- Retriable: Transient API failure → retry with backoff, log each attempt
- Skippable: Single post generation failed → log, skip this post, continue with others
How To: Add a New LLM Provider
- Create
includes/providers/class-{name}-provider.php - Implement
PRAutoBlogger_LLM_Provider_Interface(seeinterface-llm-provider.php) - Required methods:
send_chat_completion(),get_available_models(),estimate_cost() - Add provider option to the settings page in
admin/class-admin-page.php - Register the provider in
class-prautoblogger.phploader - Update ARCHITECTURE.md external API integrations table
Interface contract:
interface PRAutoBlogger_LLM_Provider_Interface {
public function send_chat_completion(array $messages, string $model, array $options = []): array;
public function get_available_models(): array;
public function estimate_cost(string $model, int $prompt_tokens, int $completion_tokens): float;
public function get_provider_name(): string;
}
How To: Add a New Source Provider (Social Platform)
- Create
includes/providers/class-{platform}-provider.php - Implement
PRAutoBlogger_Source_Provider_Interface(seeinterface-source-provider.php) - Required methods:
collect_data(),get_source_type(),validate_credentials() - Add API credentials fields to the settings page in
admin/class-admin-page.php - Register the provider in
class-prautoblogger.phploader - Add the source type string to the
ab_source_data.source_typeallowed values - Update ARCHITECTURE.md file tree and external API integrations table
Interface contract:
interface PRAutoBlogger_Source_Provider_Interface {
public function collect_data(array $config): array; // Returns PRAutoBlogger_Source_Data[]
public function get_source_type(): string; // e.g., 'reddit', 'tiktok'
public function validate_credentials(): bool;
public function get_rate_limit_status(): array;
}
How To: Add a New Admin Setting
- Define the option key in the settings array in
admin/class-admin-page.php→get_settings_fields()method - Format: add an entry to the appropriate section array:
[ 'id' => 'prautoblogger_new_setting', 'label' => __('Setting Label', 'prautoblogger'), 'type' => 'text', // text, number, select, textarea, checkbox, password 'default' => '', 'description' => __('Help text shown below the field.', 'prautoblogger'), 'sanitize' => 'sanitize_text_field', // sanitization callback ] - The settings page renderer handles registration, rendering, and sanitization automatically from this array.
- Access the setting anywhere with:
get_option('prautoblogger_new_setting', $default) - For encrypted settings (API keys): use
PRAutoBlogger_Encryption::encrypt()/decrypt()
How To: Add a New Pipeline Stage
The writing pipeline is an ordered array of stages. Each stage is a class method on Content_Generator.
- Add the stage method to
class-content-generator.php:protected function stage_{name}(PRAutoBlogger_Content_Request $request): string - Register the stage in the
get_pipeline_stages()method for the appropriate mode (single_pass or multi_step) - Each stage receives the accumulated context and returns its output
- Each stage logs its cost via
Cost_Tracker::log_api_call() - If a stage fails after retries, the entire pipeline fails (no partial publishes)
How To: Add a New Frontend Component
Frontend components use wp.element (WordPress-bundled React) — no JSX, no build step.
-
Create the PHP class in
includes/frontend/class-{name}.php:- Register a shortcode via
add_shortcode()in anon_register_shortcode()method - Register a REST endpoint if the component needs async data (in
on_register_rest_route()) - Enqueue JS (
wp-elementdependency) and CSS viawp_enqueue_script/style() - Pass config to JS via
wp_localize_script()
- Register a shortcode via
-
Create JS in
assets/js/{name}.js:- Use
wp.element.createElement(aliased asel) for rendering - Import hooks:
var useState = wp.element.useState; - Read config from
window.{localizedObjectName} - Handle all states: loading, loaded, empty, error
- Mount into the shortcode's
<div id="...">mount point
- Use
-
Create CSS in
assets/css/{name}.css:- Use Peptide Starter theme CSS custom properties with fallbacks:
var(--color-text-primary, #f9fafb) - Prefix all classes with
prab-to avoid collisions - Include responsive breakpoints, focus-visible states, and loading skeletons
- Use Peptide Starter theme CSS custom properties with fallbacks:
-
Wire hooks in
class-prautoblogger.php:- Instantiate the PHP class in
register_frontend_hooks() - Register shortcode on
init, REST routes onrest_api_init
- Instantiate the PHP class in
-
Add
'frontend/'to the autoloader's$directoriesarray (already done for the first component) -
Update ARCHITECTURE.md file tree and add a data flow section
REST endpoint conventions:
- Namespace:
prautoblogger/v1 - Permission callback: use a filter hook for extensibility (e.g.,
prautoblogger_rest_{name}_public) - Sanitize all params with
sanitize_callbackin arg registration - Return only the fields the frontend needs — no unnecessary data
- Set
Cache-Controlheader for cacheable responses
Code Quality Rules
declare(strict_types=1);in every PHP file- Type declarations on all method parameters, return types, and class properties
- No file exceeds 300 lines. Split when approaching this limit
- Every class has a 3-question preamble docblock: What / Who triggers / Dependencies
- Every public method has a docblock with
@param,@return, and side effects @seereferences at the top of files that participate in multi-class flows- All strings translatable via
__()/_e()withprautobloggertext domain - No magic methods without explicit justification documented in code
- No
echoof raw data — always escape withesc_html(),esc_attr(), etc. - Nonce on every form/AJAX —
wp_nonce_field()/check_ajax_referer()
Git Workflow — PR-Gated, Soft-Enforced
This repo is private on GitHub's free plan, which does not support branch protection or rulesets. The review gate is enforced at the agent layer, not server-side. Every agent and human contributor follows these rules; the .github/workflows/main-push-audit.yml tripwire opens an audit issue on any direct push to main that did not come from a merged PR.
Rules
-
Never push to
maindirectly. Every change lands via a pull request that the maintainer merges. No self-merging. No force-pushing tomain. -
Branch naming:
claude/<scope>-<YYYYMMDD>for agent-authored work,fix/<scope>orfeat/<scope>for human-authored. The scope is a 1–3 word kebab-case description. -
Commit trailer: every commit authored by an agent must end with an
Agent-Session:trailer so commits can be correlated back to the conversation that produced them, even though all commits share thepeptiderepobot identity.feat: add per-request cost tracking Agent-Session: cowork-2026-04-14-cost-audit -
PR description template: every PR description covers
- What changed (one paragraph)
- Why (motivation or incident link)
- Risk flags (schema changes, API contract changes, cost impact, compatibility)
- Test plan (what was run locally, what to smoke-test after merge)
-
Emergency push exception: if a situation genuinely requires pushing to
mainwithout a PR (site down, CI broken, one-line hotfix), surface it in the chat before doing it. Every emergency push gets a follow-up PR that commits the same changes through the normal flow so git stays the source of truth. The tripwire will open an audit issue — close it with a comment explaining why.
Opening a PR from an agent session
The gh CLI is not installed in the Cowork sandbox. Use curl with the PAT from the workspace .env.credentials:
GH_TOKEN=$(grep "^GITHUB_PAT=" "$WORKSPACE/.env.credentials" | cut -d= -f2)
git push -u origin HEAD
curl -s -X POST -H "Authorization: token $GH_TOKEN" \
-H "Content-Type: application/json" \
"https://api.github.com/repos/peptiderepo/<repo>/pulls" \
-d "$(jq -cn --arg title "<title>" --arg head "<branch>" --arg body "<body>" \
'{title:$title, head:$head, base:"main", body:$body}')"
When this changes
If the peptiderepo GitHub account is ever upgraded to Pro (or the repo goes public), replace this soft-enforcement section with a note pointing to the real branch protection rules in repo settings.