QA caught this on d69f637 — AGENT-OPERATING-STANDARD.md §2 requires
typed params and return types on every new public method. PHP 7.4+
supports both. Matches the pattern of other includes/ classes.
- PSA_Upgrade::is_needed( string, string ): bool
- PSA_Upgrade::maybe_run(): void
Agent-Session: cowork-qa-p1-fixes-20260414
CI on 61cf00c failed PHPUnit with "Call to undefined function
plugin_dir_path()" — the test required peptide-search-ai.php, which calls
plugin_dir_path() at top level (line 19) before any hook. Stubbing every
top-level WP function the plugin file touches was the wrong shape of fix.
Proper fix: move the upgrade logic into includes/class-psa-upgrade.php so
the test can require just the helper class, matching the pattern every
other includes/ module follows.
- New: includes/class-psa-upgrade.php with PSA_Upgrade::is_needed()
(pure) and PSA_Upgrade::maybe_run() (driver). OPTION_NAME and
DEFAULT_STORED_VERSION exposed as constants.
- peptide-search-ai.php: psa_admin_init() now calls PSA_Upgrade::maybe_run();
the inline helper functions and docblocks are removed.
- tests/bootstrap.php: loads class-psa-upgrade.php alongside the other
tested classes.
- tests/unit/UpgradeTest.php: rewritten against PSA_Upgrade; adds two
extra assertions on the constants for stability.
- ARCHITECTURE.md: refreshed to reference the class and its constants.
No behavioral change. The cost-tracker schema upgrade still runs once per
admin_init when PSA_VERSION advances past psa_db_version.
Agent-Session: cowork-qa-p1-fixes-20260414
QA review of 88a4ae9 caught that UpgradeTest.php requires peptide-search-ai.php
(to access the pure helper psa_upgrade_needed) but did not stub add_action or
add_filter. The plugin file calls those at top level (lines 47, 53, 146, 265),
so the test would fatal on load with "Call to undefined function add_action".
Follows the stub pattern used in PostTypeTest.php.
Agent-Session: cowork-qa-p1-fixes-20260414
Addresses the three P1 findings from QA baseline review of a8a0ce2:
1. uninstall.php: delete psa_db_version option on plugin removal so no
orphaned data is left behind — satisfies AGENT-OPERATING-STANDARD
teardown/uninstall DoD.
2. Extract psa_upgrade_needed( $stored, $current ) as a pure function and
add tests/unit/UpgradeTest.php covering fresh-install, same-version,
future-version, and older-version scenarios. The impure wrapper
psa_maybe_run_upgrades() now delegates the decision to the pure helper.
3. Document the psa_db_version option and the admin_init-triggered
upgrade mechanism in ARCHITECTURE.md (Options section), including
why admin_init, downgrade behavior, cleanup, and testability.
No behavioral change for end users — same upgrade semantics, just
testable and documented, with proper teardown.
Agent-Session: cowork-qa-p1-fixes-20260414
* ci: cost cap + 3-model A/B harness on CTO reviewer
Two enhancements to the PR reviewer:
1. Monthly spend cap: tracked in Repo Variables (CTO_REVIEW_MONTH,
CTO_REVIEW_SPEND_CENTS, CTO_REVIEW_CAP_CENTS). Default cap $10/month.
Auto-resets on month rollover. Posts a skip-comment on PRs when cap hit.
2. A/B harness: reviews every PR with 3 models in parallel
(Claude Haiku 4.5, DeepSeek V3.1, Kimi K2 Thinking). Comment shows
all 3 verdicts side-by-side so we can compare quality/cost on real
PRs before picking a permanent default. Drop to 1 model by editing
the MODELS list after A/B window closes.
Agent-Session: cowork-reviewer-ab-20260414
* ci: use CTO_ADMIN_TOKEN for repo variables + restore formal APPROVE/REQUEST_CHANGES
Default GITHUB_TOKEN cannot mutate Repository Variables (always 403).
Use a PAT-backed secret CTO_ADMIN_TOKEN for the budget counter steps.
Also restores formal APPROVE/REQUEST_CHANGES verdicts now that repo
setting can_approve_pull_request_reviews is enabled.
Agent-Session: cowork-reviewer-ab-20260414
* ci: address review feedback — per-model timeout + simpler cost math
- Per-model timeout of 60s so one hung model does not block siblings
- Simplify cost_cents math (cost_usd * 100 directly, no double-rounding)
Defer: fine-grained PAT for CTO_ADMIN_TOKEN (requires manual OAuth device flow).
Agent-Session: cowork-reviewer-ab-20260414
* ci: stop exposing CTO_ADMIN_TOKEN as env var (security cleanup)
Pass the PAT only via github-token param. continue-on-error on the
budget step so a missing/expired secret degrades gracefully instead
of failing the whole workflow.
Agent-Session: cowork-reviewer-ab-20260414
GITHUB_TOKEN cannot formally approve PRs without a repo setting toggle.
Verdict is still prominent in the first line of the review body, and
the human merges based on reading it — no behavior loss.
Agent-Session: cowork-reviewer-fix-20260414
* 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.
Adds psa_maybe_run_upgrades() that compares stored psa_db_version
against PSA_VERSION on admin_init. When the code version is newer,
re-runs PSA_Cost_Tracker::create_table() so dbDelta picks up additive
schema changes (like the new token_source column) without requiring
the admin to deactivate/reactivate the plugin.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DeepSeek models on OpenRouter omit the usage object, causing all token
counts and costs to log as zero. Added character-based estimation
fallback (~3.75 chars/token), token_source column to distinguish
measured vs estimated data, and admin UI indicators (amber warnings,
~ prefix on estimates). Updated DeepSeek/Qwen pricing table.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
is_budget_exceeded() used `0 === $budget` but floatval() returns float,
so 0 (int) === 0.0 (float) is always false. This caused unlimited
budget ($0) to be treated as exceeded, blocking all AI generation.
Same bug existed in the admin display (showed "$0.00" instead of
"Unlimited" for budget remaining).
This was a P1 bug that prevented ALL AI content generation since v4.3.0.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The daily generation cap is designed for automated cron-based processes.
When an admin explicitly clicks Re-enrich, they accept the cost. The AJAX
enrichment is already rate-limited by a 2-second polling interval and
still respects the monthly budget check in generate_peptide_content().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the WP-Cron-based re-enrichment with an AJAX polling approach
that processes one peptide per request. This bypasses WP-Cron entirely,
which is unreliable on Hostinger shared hosting with LiteSpeed + Cloudflare.
New files:
- includes/class-psa-batch-enrichment.php — AJAX endpoints for status + process
- assets/js/psa-batch-enrich.js — Admin progress bar UI with polling loop
The admin settings page now shows a "Re-enrich Peptides" button with live
progress bar, per-item log, and automatic retry on failure. Respects the
daily generation cap and monthly budget.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix multi-item associative array on single line in class-psa-admin.php
(line 273) and remove blank line before closing class brace in
class-psa-ai-generator.php (line 540).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move declare(strict_types=1) after file-level docblock in all 12 class
files to satisfy WordPress Coding Standards docblock placement rule.
Fix multi-line function call formatting in class-psa-admin.php. Align
array double arrows and equals signs. Fix escaped backticks in
AIGeneratorTest heredoc that broke markdown fence stripping tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds [peptide_directory] shortcode with card grid, category taxonomy (8 terms),
search, pagination, detail modal, and /v1/compounds REST endpoint. Includes 9
new meta fields (half-life, stability, solubility, etc.), admin data tools for
migration/re-enrichment, integration hooks for cross-plugin extensibility,
dark mode support, and full uninstall cleanup. Refactors AI generator into
PSA_OpenRouter + PSA_KB_Builder classes. Adds PHPUnit tests for PostType and
Directory classes. Fixes QA findings: dead JS selector, renderError loading
state, numeric meta validation, prepared SQL in uninstall.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace interpolated variables in SQL strings with concatenation
and add appropriate phpcs:ignore comments for safe table name usage
built from $wpdb->prefix. Fixes CI failure on WordPress Coding
Standards check.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create PSA_Cost_Tracker class with database table for API call logging
- Log prompt/completion tokens and estimated cost for every OpenRouter API call
- Implement monthly budget system with hard stop (budget of 0 = unlimited)
- Add budget checks in validate_peptide_name() and generate_peptide_content()
- Add dry_run() method to estimate costs before making API calls
- Display monthly usage summary and recent API logs in admin settings
- Add monthly_budget setting to admin interface with number input field
- Update uninstall.php to drop psa_api_logs table on plugin removal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create PSA_Encryption class with AES-256-CBC encrypt/decrypt methods
- Derive encryption key from wp_salt('auth') using SHA-256 hash
- Prepend IV to ciphertext and base64-encode for safe storage
- Update PSA_Admin to encrypt API keys on save, mask on display
- Preserve existing key when empty/mask value submitted (no overwrite)
- Update PSA_AI_Generator to decrypt API key on read from database
- Maintain backward compatibility with plaintext keys (auto-encrypt on next save)
- Prefer wp-config constant over database value for maximum security
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SEC-001 CRITICAL: Remove spoofable X-Forwarded-For/X-Real-IP from IP detection; trust only CF-Connecting-IP + REMOTE_ADDR
SEC-004 HIGH: Add peptide name input validation with character whitelist and prompt injection blocking
SEC-006 HIGH: Add missing return after wp_send_json_success in handle_pending_retry max-retries block
SEC-007 MEDIUM: KB articles now respect auto_publish setting instead of always publishing
SEC-009 MEDIUM: Replace hardcoded page ID 73 with configurable kb_page_id setting
SEC-010 MEDIUM: Fix cache invalidation — add underscore prefix to transient name pattern
SEC-011 MEDIUM: Skip sleep() during WP-Cron to avoid blocking PHP workers on rate limit retry
SEC-012 MEDIUM: Add global daily generation cap (50/day) to prevent cost overruns
SEC-013 LOW: Add @deprecated notice to unused PSA_Error class
SEC-015 LOW: Convert uninstall.php from space to tab indentation (WPCS)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All 499 PHPCS errors were 'Tabs must be used to indent lines; spaces are not allowed'.
Converted 4-space indentation to tabs throughout class-psa-ai-generator.php.