Four more 'single' keys had incorrect tab counts (same over-indentation
pattern as line 37 fixed in prior commit):
- Line 52 (verdict_text registration): 6 tabs → 3
- Line 66 (signal_row text, inside for loop): 8 tabs → 4
- Line 80 (signal_row glyph, inside for loop): 8 tabs → 4
- Line 97 (has_partner_link): 6 tabs → 3
For-loop body lines 173–175: were at 3 tabs (same level as the 'for'
statement); PHPCS requires body to be indented one level deeper → 4 tabs.
All pre-existing violations in verdict-meta.php are now cleared.
front-page.php:
- Replace 3 decorative multi-line HTML comment blocks (tab+space mixed
indentation) with single-line <!-- SECTION --> comments; eliminates 16
'Tabs must be used' PHPCS errors at lines 37-41, 93-97, 127-132
inc/verdict-meta.php:
- Line 37: correct over-indented 'single' key (6 tabs → 3 tabs to match
array alignment of sibling keys)
- Line 171: split '<?php for (...)' onto two lines so opening PHP tag is
on a line by itself (WordPress PHPCS requirement)
Both are pre-existing violations now exposed because CI runs PHPCS on all
PHP files (not just changed ones). No functional change.
The verdict card was showing 'Updated [WP post creation date]' which doesn't
reflect the editorial last-reviewed date. Showing a wrong date is worse than
showing none. Timestamp removed; will re-add with last_reviewed_date post meta
in v1.1 when the review-cadence tracking system is ready to populate it.
P1.1: verdict_text meta field + card.php wiring
- Register verdict_text (textarea, show_in_rest) on peptide CPT
- card.php reads verdict_text meta; falls back to placeholder if empty
- Hardcoded generic placeholder no longer the default for published posts
- All 3 monographs populated with CMO-authored verdict text
P1.2: per-row glyph meta fields
- Register signal_row_1/2/3_glyph (enum: ✓|!|?|⊘|⚠, show_in_rest)
- card.php reads per-row glyph from meta instead of hardcoding by position
- Missing glyph renders nothing (not a wrong glyph)
- Semaglutide: ✓✓! | BPC-157: ✓!? | Melanotan-II: !!⚠
P1.3: conditional inline disclosure
- Register has_partner_link (boolean, default false, show_in_rest)
- single-peptide.php only renders inline disclosure when has_partner_link=true
- All 3 loaded monographs set to has_partner_link=false
- Footer disclosure unaffected (always renders)
N.1: dark mode toggle hidden
- header.php: toggle button hidden (display:none + aria-hidden)
- Dark-mode verdict colors not validated for v1; re-enable in v1.1
Meta box updated with textarea, glyph dropdowns, and partner-link checkbox.
The engineer registered verdict meta and the meta box against 'pr_peptide'
which does not exist. PR Core registers the CPT as 'peptide'. Result: the
verdict meta box was invisible in wp-admin and meta was not exposed in REST.
All functional register_post_meta and add_meta_box calls now target 'peptide'.
No data migration needed — post meta is keyed by meta_key, not CPT.
- Verdict card component was built in alpha.1 but never called from the
template; no verdict state was rendering on any pr_peptide page.
Now called immediately after the page header for posts with verdict_state.
- Affiliate disclosure (inline) was built in alpha.1 but never called.
Now rendered after .peptide-content-box (below the_content / partner links)
per the editorial disclosure spec: disclosure must follow the partner link.
- Suppress page-header <h1> when verdict_state is set; the verdict card
already renders an <h1>, avoiding duplicate heading semantics / SEO issue.
Remove all WC asset dequeue logic, cart icon conditional, and WC-specific tests from the theme. Eliminates the class of fatal errors seen in the 2026-04-23 incident where is_woocommerce() was called after WC deactivation.
Version: 1.6.x → 1.7.0
Reviewed-by: peptiderepo (CTO)
QA: qa-reviews/peptide-starter-theme/2026-04-23-d239bd1e.md
Two safety nets to prevent rsync --delete wiping the wrong directory:
1. Pre-deploy: verify destination/style.css exists (proves it's a theme dir)
2. Post-deploy: compare active plugin count before and after — fail if any dropped
Addresses root cause of 2026-04-23 theme deploy incident.
Call to undefined function is_woocommerce() in perf-asset-policy.php:69
caused a 500 on all page loads. WooCommerce is not active on this site.
Added function_exists() guard so the WC asset dequeue block is skipped
when WooCommerce is not installed.
When a PR modifies the admin settings surface, file/class structure, or data flow, the QA reviewer now flags the PR as a MAJOR concern unless the PR description references an accompanying update to the CTO app-map at `Peptide Repo CTO/app-maps/<repo-slug>.md`.
The CTO workspace is a separate repo, so CI cannot verify the doc update mechanically — the reviewer inspects the PR description for an explicit mention.
Why: the CTO-level app-maps are the single-pane-of-glass reference that lets the CTO answer questions like 'which settings does this plugin expose?' without grepping the repo each time. Without a merge-gate prompt, these maps drift out of sync with the code.
Co-authored-by: peptiderepo <peptiderepo@users.noreply.github.com>
The theme was rendering featured images at WordPress 'medium' size
(300×300 square crop) instead of the full 1200×632 original. Changed
the_post_thumbnail() from 'medium' to 'full' and added responsive CSS
that scales images to 100% content width with centered alignment.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The peptide-starter-theme v1.5.2 release added
peptide_starter_is_cloudflare_peer() — a CF-peer IP validator that
correctly rejects direct-to-origin requests as non-Cloudflare traffic
with HTTP 403. The deploy workflow's post-deploy health check makes
exactly such a request from a GHA runner IP, so it now reliably
receives 403 even on successful deploys.
Two deploys today (peptide-starter-theme v1.5.2, prautoblogger Commit 1a)
both succeeded but reported workflow failure due to this. The actual
deploys (rsync + cache purge) ran clean; site verified HTTP 200 from
real traffic both times.
Fix: accept either 200 (normal healthy response) or 403 (validator
actively rejecting non-CF origin — itself a positive liveness signal)
in the health check. Reject only on 5xx, 4xx-other, or connection
failure.
Reverting this fix is correct only if we either (a) bypass the
validator for our own health-check user-agent, or (b) route the
health check through Cloudflare. Both are larger changes for a
later release.
Agent-Session: cowork-deploy-healthcheck-fix-20260415
Co-authored-by: peptiderepo <peptiderepo@users.noreply.github.com>
Neurometric markets a task-specialized PR-Summarizer SLM (Qwen3-4B +
specialist system prompt) on a free tier. Adding it as a 4th voice in
the existing A/B reviewer panel to see whether a specialized small
model catches things the 3 general-purpose voices miss.
The SLM does not honor OpenAI tool-calling, so it cannot return our
structured submit_review schema. Wired as a "prose voice":
- MODELS tuple gains a 6th provider_type field
("openrouter_tools" | "neurometric_prose")
- call_model() dispatches; new call_neurometric_prose() path hits
api.neurometric.ai/v1 without tools, embeds raw markdown output,
and infers verdict from keyword heuristic
- Render branch in PR comment marks the verdict as heuristic and
notes tokens/cost are not tracked (free tier)
- Never the primary voice; heading still driven by Haiku
Also hardens the shared render path against models that omit the
optional 'summary' field (rev.get('summary') fallback).
Requires new repo secret NEUROMETRIC_API_KEY (already set on all four
peptiderepo repos). Voice self-skips with inline error if missing.
Agent-Session: cowork-neurometric-qa-voice-20260415
Co-authored-by: peptiderepo <peptiderepo@users.noreply.github.com>
Adds the org-wide agent process doc (QA gate, peptiderepo git identity,
300-line cap, doc-in-same-commit, branch naming, commit trailer, PR-only
to main) and rewrites CLAUDE.md as a thin bootstrap pointer to it.
No runtime impact. No code change. Pure process/docs ratification.
Per CTO handoff in convo/prtheme/threads/2026-04-v1.5.2-release-cleanup/
03-cto-reply.md, this is the calibration run for the rollout pattern
across the four peptiderepo repos.
Agent-Session: prtheme/v1.5.2-release-cleanup/claude-opus-4-6
Adds a pointer to the cross-app engineering docs in the CTO workspace so engineers scoped to this repo can find shared reference material (Cloudflare AI Gateway routing, OpenRouter key rotation, interface pattern, image-generation stack, social distributor choice) and the incident runbook.
No code changes. Pure docs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* 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
* ci: add PHPUnit workflow that runs the existing test suite
59 tests exist across 8 files but were never running in CI.
This adds a ci.yml with PHP Lint + PHPCS + PHPUnit against WP test
framework on a MySQL service.
Agent-Session: cowork-pr-theme-tests-20260414
* ci: vendor install-wp-tests.sh for the PHPUnit workflow
Standard WP-CLI scaffold script, lightly hardened. Kept in-repo
instead of curl-from-github so CI is reproducible.
Agent-Session: cowork-pr-theme-tests-20260414
* chore: retrigger CI after reviewer fix landed on main
Agent-Session: cowork-pr-theme-tests-20260414
* ci: install subversion before bootstrap (svn no longer preinstalled)
ubuntu-latest dropped svn from the default image. install-wp-tests.sh
still uses svn to fetch the WP test suite from develop.svn.wordpress.org.
Add a step to apt-install subversion before the bootstrap runs.
Agent-Session: cowork-pr-theme-tests-20260414
* ci: skip db creation in install-wp-tests.sh (MySQL service already created it)
Agent-Session: cowork-pr-theme-tests-20260414
* ci: WP_CORE_DIR needs trailing slash for ABSPATH substitution
The wp-tests-config.php substitution copies WP_CORE_DIR into ABSPATH
verbatim, then later WordPress concatenates wp-settings.php. Without
the trailing slash you get /tmp/wordpresswp-settings.php.
Agent-Session: cowork-pr-theme-tests-20260414
* ci: post CTO review as COMMENT instead of APPROVE
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
* fix(ci): ssh-keyscan failure no longer fails the deploy
ssh-keyscan was being called with set -e in effect and stderr suppressed,
so a single keyscan failure killed the entire deploy with no diagnostic
output. Since rsync/ssh use StrictHostKeyChecking=no, the known_hosts
entry is not load-bearing — making the keyscan best-effort is safe.
stderr is now captured to /tmp/keyscan.err and emitted as a warning so
future failures are debuggable.
Agent-Session: cowork-deploy-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.
Security follow-up to v1.5.1. Closes four items from the v1.5.1 review —
two ship-blockers and two mediums — all within the ADR-0001 envelope.
PSEC-007 — peptide_starter_get_client_ip() now trusts CF-Connecting-IP
only when REMOTE_ADDR falls inside a published Cloudflare edge range.
Previously, any direct-to-origin request (which Hostinger permits) could
forge its rate-limit key by setting the header to a different IP per
attempt, bypassing every abuse control in the theme. New module
inc/cloudflare-ips.php embeds a dated snapshot of CF v4/v6 ranges and a
CIDR matcher covering both families; list is overridable via the
peptide_starter_cloudflare_ip_ranges filter for out-of-band updates.
HTTP_X_FORWARDED_FOR stays ignored unless operators opt in via the
peptide_starter_trust_xff filter.
PSEC-008 — Removed the v1.5.1 user-verification migration (Option A as
confirmed by peptiderepo). The sync-wp_mail-per-subscriber loop on
admin_init would have blocked wp-admin and hammered deliverability.
Existing accounts are now grandfathered: no ps_pending_verification
meta = verified. Verification applies only to registrations from v1.5.2
forward. The ps_verify_migration_version option (written by v1.5.1) is
cleared on theme activation.
PSEC-009 — Registration validation no longer short-circuits. All five
checks (username pattern, email validity, password length,
username_exists, email_exists) evaluate unconditionally before the
combined rejection. Closes the response-time oracle that let an
attacker distinguish microsecond-fast regex rejection from DB-round-trip
rejection and infer which field triggered the failure.
PSEC-010 — Added the happy-path verify test that v1.5.1 was missing.
Covers token match, user meta cleanup, auth cookie set, and the
peptide_starter_user_verified action firing with the correct user ID.
Seven new rate-limiter tests confirm CF-peer validation works
end-to-end: trusted CF peer honours the header, untrusted peer ignores
spoofed headers, invalid values fall through, XFF default-off, XFF
filter opt-in, CF range filter override, IPv6 CIDR match, family
mismatch rejection.
Confirmed with peptiderepo before coding: (1) Option A for PSEC-008
because zero users; (2) Cloudflare IS in front of the origin (dig
confirms 104.21.x / 172.67.x + CF-Ray response header), so PSEC-007 is
a real fix not dead code; (3) no staging — treat prod as staging.
ARCHITECTURE.md gains a client-IP trust model section; CONVENTIONS.md
documents the header-trust rules for future modules.
Version bumped to 1.5.2 in style.css and functions.php.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Security-only release per ADR-0001. Closes all six ship-blockers from the
post-v1.5.0 audit: PSEC-001 (no rate limiting), PSEC-002 (account
enumeration via separate error messages), PSEC-003 (registration auto-login
with no email verification), PSEC-004 (ungated "My Data" templates),
PSEC-005 (CSV formula injection on subscriber export), PSEC-006 (no
honeypot/CAPTCHA on public forms).
Adds inc/config.php (single source of truth for all security thresholds,
filterable via peptide_starter_security_config), inc/rate-limiter.php
(Peptide_Starter_Rate_Limiter + Cloudflare-aware client IP resolver,
transient-backed, never stores raw IPs), and inc/email-verification.php
(43-char token, 24h TTL, /verify rewrite, rate-limited resend endpoint).
Auth handlers rewritten to: unify all failure messages, rate-limit before
any expensive check, drop registrations with a filled honeypot silently,
stop auto-login on register, send a verification email instead. The
require_login() helper lives in inc/helpers.php and is applied at the top
of page-tracker.php, page-subject-log.php, and page-protocol-builder.php;
page-calculator.php stays open (no PII, no writes).
Newsletter signup gains: consent checkbox, privacy link, honeypot,
autoload=false on the wp_options blob, unsubscribe token stored per row,
/newsletter-unsubscribe route, merged success+duplicate into a single "ok"
state. Admin export runs every cell through peptide_starter_csv_safe()
which prefixes leading =/+/-/@/tab/CR with a quote.
Contact handler hardens against email-header injection (rejects name
values with \r \n , < >), rate-limits, honors honeypot, tags mail
failures with a request ID for correlation without logging message
content.
Adds Tools → Mail Test (inc/mail-diagnostic.php): a permanent wp_mail
probe so we can check Hostinger outbound deliverability any time it looks
flaky. Adds a one-time admin_init migration that enrols pre-v1.5.1
subscribers into the verification flow (currently a no-op — zero users —
but guarded for the future).
Other cleanup from the audit: fallback primary menu moved out of
header.php into inc/helpers.php (redeclaration risk), placeholder footer
language links replaced with "Translations coming soon", footer copyright
now reads through the customizer getter, settings-panel gains a proper
Tab/Shift+Tab focus trap and saves/restores body overflow, documentation
heading IDs slugified from heading text with collision counters, JS files
standardized on const/let.
Six new PHPUnit suites cover rate limiter lifecycle, auth handler error
unification and rate limits, email verification token generation and
expiry, contact handler header injection + rate limit + honeypot,
newsletter signup consent + CSV safety, and require_login gate behavior.
ARCHITECTURE.md and CONVENTIONS.md updated in the same commit — new
module layout, data flows, security patterns for rate-limiting,
honeypotting, gating, and CSV-safe export.
Version bumped to 1.5.1 in style.css and functions.php.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reformat add_setting() and add_control() calls to comply with
PEAR.Functions.FunctionCallSignature — each argument on its own line,
opening paren last on line, closing paren on its own line. Matches the
pattern already used by add_section() calls in the same file.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Major theme extension adding full-featured navigation with dropdown submenus (Tools, My Data, Resources),
9 new page templates (Calculator, Protocol Builder, Tracker, Subject Log, Documentation, Directory,
Science Feed, Profile, Sign In/Register), documentation page with sidebar ToC and scroll spy,
branded AJAX authentication forms, newsletter signup with admin export, slide-out contact/support panel,
Research Modules 6-card grid on front page, and comprehensive footer update.
Split functions.php (554→206 lines) into 6 focused inc/ modules: helpers, customizer, auth-handlers,
contact-handler, page-setup, newsletter-admin. All new components include dark mode support, CSRF
protection, input sanitization, output escaping, and graceful plugin degradation via shortcode_exists().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reduce hero padding from 96/80px to 64/48px, shrink subtitle and
search margins, tighten news-feed and archive section padding,
and reduce peptides grid gap for better content density.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Warm off-white light palette, decorative geometric shapes,
category-coded peptide cards with tinted borders and pill badges,
larger hero typography with accent word, header CTA button,
and redesigned single-peptide detail page with meta card grid.
Dark mode stays default per user preference. Light mode gets the
warm treatment when toggled or via prefers-color-scheme.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The CI was overriding the project PHPCS config by passing
--standard=WordPress-Core directly. Now uses the project
.phpcs.xml.dist which includes exclusions for standard WP
patterns (class in functions.php).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Convert all PHP files from CRLF to LF line endings
- Reformat multi-line function calls per WPCS standard
(opening paren last on line, closing paren on own line)
- Fix assignment-in-conditional in single-peptide.php
- Change double quotes to single quotes in content-none.php
- Exclude InvalidClassFileName sniff (standard WP functions.php pattern)
- Fix archive-peptide.php opening PHP tag placement
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wrapped get_search_query() with esc_attr() in 404.php, front-page.php,
and searchform.php. Without this, a crafted search query can inject
arbitrary HTML attributes into the search input value field.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Renders PRAutoBlogger-generated research articles as a card grid
between the hero section and the peptide news feed. Wrapped in
shortcode_exists() so it degrades gracefully if the plugin is
deactivated.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>