Files
wp-healthcheck/CLAUDE.md
Steve Hanlon f551b64e2e CLAUDE.md: replace placeholder sections with real project context
The Build & Test, Architecture Overview, and Conventions & Patterns sections
were still the init template's "_Add your ... here_" stubs. Filled them in
from the code:

- No build/test tooling exists; documents the php -l quality gate and the
  dev-router.php requirement for running the server locally.
- Describes the two-deployable split (WP plugin vs server/) and the
  .gitattributes export-ignore rule that keeps them apart in release zips.
- Records the PHP 7.4 (plugin) / 8.1+ (server) version split that already
  drifted once in 3c64dd8.
- Notes Gitea (not GitHub) as the remote, the three-file release bump, and
  the open security beads to check before touching auth or the installer.

The managed beads integration block is unchanged.
2026-07-23 09:09:11 +01:00

8.7 KiB

Project Instructions for AI Agents

This file provides instructions and context for AI coding agents working on this project.

Beads Issue Tracker

This project uses bd (beads) for issue tracking. Run bd prime to see full workflow context and commands.

Quick Reference

bd ready              # Find available work
bd show <id>          # View issue details
bd update <id> --claim  # Claim work
bd close <id>         # Complete work

Rules

  • Use bd for ALL task tracking — do NOT use TodoWrite, TaskCreate, or markdown TODO lists
  • Run bd prime for detailed command reference and session close protocol
  • Use bd remember for persistent knowledge — do NOT use MEMORY.md files

Session Completion

When ending a work session, you MUST complete ALL steps below. Work is NOT complete until git push succeeds.

MANDATORY WORKFLOW:

  1. File issues for remaining work - Create issues for anything that needs follow-up
  2. Run quality gates (if code changed) - Tests, linters, builds
  3. Update issue status - Close finished work, update in-progress items
  4. PUSH TO REMOTE - This is MANDATORY:
    git pull --rebase
    bd dolt push
    git push
    git status  # MUST show "up to date with origin"
    
  5. Clean up - Clear stashes, prune remote branches
  6. Verify - All changes committed AND pushed
  7. Hand off - Provide context for next session

CRITICAL RULES:

  • Work is NOT complete until git push succeeds
  • NEVER stop before pushing - that leaves work stranded locally
  • NEVER say "ready to push when you are" - YOU must push
  • If push fails, resolve and retry until it succeeds

Build & Test

There is no build step, no package manager, and no test suite. Plain PHP on both sides; the only third-party code is a vendored copy of Plugin Update Checker under vendor/plugin-update-checker/ (do not hand-edit it — see README for the refresh procedure).

Quality gate for a change is a syntax lint of the files you touched, against the right PHP version for that half of the repo (see Conventions):

# Plugin — must parse on PHP 7.4
find . -path ./vendor -prune -o -path ./server -prune -o -name '*.php' -print \
  | xargs -n1 php7.4 -l          # fall back to `php -l` if 7.4 isn't installed

# Server — PHP 8.1+
find server -name '*.php' -print0 | xargs -0 -n1 php -l

Run the history server locally:

cp server/config.php.example server/config.php   # set api_key + db_dsn (sqlite is easiest locally)
php -S 127.0.0.1:8765 server/dev-router.php
curl 127.0.0.1:8765/                             # → {"ok":true,...}

Use dev-router.php, not -t server/public. Healthcheck ids are dotted uniqid() values and PHP's built-in server treats dotted path segments as static file requests, so the plain docroot form 404s on most real URLs.

The plugin has to be exercised inside a WordPress install — symlink the repo into wp-content/plugins/att-site-healthcheck and use Tools → Site Healthcheck. It refuses to run until ATT_HC_API_URL / ATT_HC_API_KEY are configured (wp-config constants win over the settings-screen options).

Server deploy is an rsync of server/ — see server/DEPLOY.md. Migrations run automatically on the first request after deploy.

Architecture Overview

Two independent deployables live in this one repo, and they must not leak into each other:

  1. The WordPress plugin — repo root, includes/, vendor/. Ships to wp-content/plugins/. Installed per engagement and uninstalled at the end.
  2. The central history server (server/) — a standalone PHP app deployed to a VPS. .gitattributes marks server/, .beads/, .claude/, AGENTS.md, CLAUDE.md, and steps.md as export-ignore, so Gitea's archive/main.zip (which is the plugin release artifact) excludes them. Any new non-plugin file at the repo root needs an export-ignore line.

Plugin

  • att-site-healthcheck.php — header, constants, requires, step discovery on plugins_loaded, PUC update checker wiring.
  • includes/steps/<order>-<slug>.php — one file per healthcheck step, each returning an ATT_HC_Step subclass instance. Adding a step is dropping a file; reordering is renaming the numeric prefix (loaded in natsort order); removing is deleting the file. ATT_HC_Steps is the singleton registry and applies the att_hc_steps filter after discovery.
  • ATT_HC_Sessionthe server is the source of truth. The att_hc_session WP option is only a cache of the active session. Every write is write-through: API call first, local cache updated only on success, so the cache can never drift. Cross-engagement history (prior notes, previous-session diff, step counts) is fetched live.
  • ATT_HC_Apiwp_remote_request client; every method throws ATT_HC_Api_Exception. Refuses non-https:// endpoints unless loopback.
  • includes/admin-page.php — the whole UI (renderers + admin_post_* handlers).
  • includes/report.php — Markdown report builder. Reports are never stored server-side; the technician downloads them.
  • includes/recovery-bootstrap.php / recovery-installer.php — step 0 detects and can install the companion wp-site-recovery plugin (detected by plugin Name + Author, not folder slug).

Server (server/)

Front controller → src/bootstrap.php (hand-rolled PSR-4 autoloader for AttHc\Server\, error/exception handlers, config.php load) → src/routes.phpRouterControllers/. Store holds all SQL; Db is a PDO singleton that runs Migrations::ensureRan() on first connection.

  • API routes take Authorization: Bearer <api_key> (Auth::require, hash_equals). GET / is deliberately public — the plugin pings it to check reachability.
  • /dashboard* is requiresAuth: false at the router level because it does its own auth: ?key=<api_key> once, then a PHP session cookie, plus a CSRF token on POSTs.
  • Storage is PDO against either MySQL 8.0+ or SQLiteconfig.php.example defaults to MySQL because pdo_sqlite is missing on some Ubuntu + Sury PHP combinations. Keep migrations/*.sql portable across both (VARCHAR(N), BIGINT, no AUTO_INCREMENT); driver-specific DDL needs dispatch in Migrations.php.

Conventions & Patterns

PHP version split — the easiest mistake to make here

  • Plugin code (root + includes/) must parse on PHP 7.4. No str_starts_with /str_contains/str_ends_with, no named arguments, no match, no nullsafe ?->, no constructor promotion, no enums. This has been broken by drift before (commit 3c64dd8) — lint against 7.4, don't just eyeball it.
  • Server code (server/) targets PHP 8.1+ and freely uses declare(strict_types=1), typed properties, never, named arguments, and promotion.

Plugin

  • Guard every file with if (!defined('ABSPATH')) exit;.
  • Admin actions go through admin_post_* handlers with current_user_can('manage_options')
    • wp_nonce_field() / check_admin_referer(), and escape on output (esc_html/esc_attr).
  • A step's id() is a stable contract — it is the key stored in session and server data. Rename files freely, never rename an id once shipped.
  • Optional step behaviour hangs off ATT_HC_Step: autocheck() for findings, render_extra() + handle_action() for step-specific UI/POSTs.
  • API failures surface as admin notices. History/diff lookups degrade silently (nice-to-have); writes must not.

Server

  • Every file: declare(strict_types=1); and namespace AttHc\Server\....
  • All SQL lives in Store, always via prepared statements.
  • Request input goes through Validate::*; responses through Http::json / Http::error ({"error":…,"code":…}).
  • No composer. If you think you need a dependency, vendor it or don't.
  • server/config.php is deployment-specific and gitignored — never commit it, and don't echo secrets into responses or logs.

Repo / release

  • Remote is Gitea (git.h12e.com/steve/wp-healthcheck), not GitHub — gh and PR workflows don't apply. Work lands on main.
  • Release = bump Version: in the plugin header and ATT_HC_VERSION and version + last_updated in updates.json, then push main. PUC polls updates.json and installs archive/main.zip, so whatever is on main is what ships. Built zips are gitignored.
  • steps.md is the human technician guide the step files are derived from; keep the two in step when step content changes.
  • Known open security issues are tracked in beads — hc-gp3 (API key exposure), hc-adg (unpinned recovery-plugin install), hc-ufl (no rate limiting / unbounded error log). Check them before touching auth, the settings screen, or the installer.