* 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.
9.2 KiB
Executable File
Peptide News Aggregator - Conventions
Project-specific patterns and step-by-step extension guides.
Naming conventions
Classes: Peptide_News_{Name} (WordPress standard, one class per file).
Files: class-peptide-news-{name}.php in includes/, admin/, or public/.
Options: All prefixed peptide_news_ (e.g., peptide_news_fetch_interval).
Transients: Prefixed peptide_news_ (e.g., peptide_news_fetch_lock).
Database tables: {$wpdb->prefix}peptide_news_{name} (e.g., wp_peptide_news_articles).
Hooks: Custom actions/filters prefixed peptide_news_ (e.g., peptide_news_cron_fetch).
CSS classes: .pn-* namespace to avoid collisions with theme and other plugins.
Constants: PEPTIDE_NEWS_* (e.g., PEPTIDE_NEWS_VERSION, PEPTIDE_NEWS_PLUGIN_DIR).
Hook registration pattern
All hooks are registered centrally in class-peptide-news.php via the Loader class. Never call add_action() or add_filter() directly in a module class. Instead, register in the appropriate define_*_hooks() method:
// In class-peptide-news.php
private function define_cron_hooks() {
$fetcher = new Peptide_News_Fetcher();
$this->loader->add_action( 'peptide_news_cron_fetch', $fetcher, 'fetch_all_sources' );
}
Error handling patterns
External API calls: Always check is_wp_error() on wp_remote_post()/wp_remote_get() responses. Return WP_Error from methods that make API calls. Log errors via Peptide_News_Logger.
AJAX handlers: Always verify nonce with check_ajax_referer( 'peptide_news_admin', 'nonce' ) and check current_user_can( 'manage_options' ) before any action. Return via wp_send_json_success() / wp_send_json_error().
Fail-open for optional features: If encryption is unavailable (no OpenSSL), store keys as plaintext. If LLM classification fails, let the article through ('editorial'). Never block core functionality due to optional feature failures.
Logging: Use Peptide_News_Logger with appropriate levels and context tags. Available contexts: fetch, llm, admin, cron, general. The logger auto-prunes at 2000 rows.
How to add a new news source
-
Add a new private method to
class-peptide-news-fetcher.php:private function fetch_new_source() { // Return array of article arrays matching this structure: // [ 'source' => 'SourceName', 'source_url' => '...', 'title' => '...', // 'excerpt' => '...', 'content' => '...', 'author' => '...', // 'thumbnail_url' => '', 'published_at' => 'Y-m-d H:i:s', 'categories' => '' ] } -
Call the method from
fetch_all_sources(), gated by an option:if ( get_option( 'peptide_news_new_source_enabled', 0 ) ) { $articles = array_merge( $articles, $this->fetch_new_source() ); } -
Add the setting to
class-peptide-news-admin.phpinget_sanitize_callback(). -
Add a field renderer method and register it in
register_settings(). -
Update
ARCHITECTURE.mdwith the new source in the data flow diagram.
How to add a new admin setting
-
In
class-peptide-news-admin.php:- Add the sanitize callback in
get_sanitize_callback(). - Add a
render_{setting_name}_field()method. - Register the field in
register_settings()using$this->register_field().
- Add the sanitize callback in
-
In
class-peptide-news-activator.php:- Add a default value in the
$defaultsarray inset_default_options().
- Add a default value in the
-
The option is automatically registered with the Settings API and will appear in the admin UI.
How to add a new LLM provider
The plugin currently talks to OpenRouter, which proxies to multiple LLM providers. To add a direct provider:
-
Create
includes/class-peptide-news-{provider}.phpimplementing the same interface asLLM::call_openrouter(): takes(string $api_key, string $model, string $prompt)and returnsstring|WP_Error. -
Add a provider selection setting to the admin page.
-
Update
LLM::process_article()to dispatch to the selected provider. -
Encrypt the new provider's API key using
Peptide_News_Encryption::encrypt()on save.
How to add a new REST API endpoint
-
In
class-peptide-news-rest-api.php:- Add the route in
register_routes(). - Add the callback method.
- Use
check_admin_permissionsfor admin-only endpoints. - Use
'permission_callback' => '__return_true'for public endpoints.
- Add the route in
-
For analytics endpoints, use the shared
get_date_range_args()for consistent parameter handling.
How to add cost tracking for a new LLM operation
-
In the method that calls the LLM API, use
call_openrouter_with_usage()instead ofcall_openrouter():$response = self::call_openrouter_with_usage( $api_key, $model, $prompt ); -
After the API call, log the cost via
Peptide_News_Cost_Tracker::log_api_call():if ( class_exists( 'Peptide_News_Cost_Tracker' ) && ! empty( $response['usage'] ) ) { Peptide_News_Cost_Tracker::log_api_call( $model, 'your_operation_name', // e.g., 'keywords', 'summary', 'filter' $response['usage'], $article_id, $response['request_id'] ?? '', $response['cost'] ?? 0.0 ); } -
Before the API call, check the budget gate:
if ( class_exists( 'Peptide_News_Cost_Tracker' ) && Peptide_News_Cost_Tracker::is_budget_exceeded() ) { // Skip the call, log a warning, return early } -
If the model isn't in
DEFAULT_MODEL_PRICING, add it to the constant array inclass-peptide-news-cost-tracker.php.
Security checklist for every change
- Sanitize all input:
sanitize_text_field(),absint(),esc_url_raw(). - Escape all output:
esc_html(),esc_attr(),esc_url(),wp_kses_post(). - Nonce on every form/AJAX:
wp_nonce_field()/check_ajax_referer(). - Capability check:
current_user_can( 'manage_options' )on every admin action. - API keys: always encrypt via
Peptide_News_Encryption::encrypt(), decrypt via::decrypt(). - Never expose API keys to the browser (all API calls are server-side only).
Database migration pattern
- Schema changes go in
Peptide_News_Activator::create_tables()usingdbDelta(). - The
plugins_loadedhook in the bootstrap file checkspeptide_news_db_versionagainstPEPTIDE_NEWS_VERSIONand re-runs activation if outdated. - Always use
$wpdb->prepare()for queries with dynamic values. - Use
// phpcs:ignorecomments for unavoidable PHPCS warnings on$wpdb->query()calls.
Type safety
All PHP files use declare(strict_types=1). All public and private methods have typed parameters and return types. This catches type errors at call time rather than at runtime and gives AI agents structural information for reasoning about code.
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.