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.
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
bdfor ALL task tracking — do NOT use TodoWrite, TaskCreate, or markdown TODO lists - Run
bd primefor detailed command reference and session close protocol - Use
bd rememberfor 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:
- File issues for remaining work - Create issues for anything that needs follow-up
- Run quality gates (if code changed) - Tests, linters, builds
- Update issue status - Close finished work, update in-progress items
- PUSH TO REMOTE - This is MANDATORY:
git pull --rebase bd dolt push git push git status # MUST show "up to date with origin" - Clean up - Clear stashes, prune remote branches
- Verify - All changes committed AND pushed
- Hand off - Provide context for next session
CRITICAL RULES:
- Work is NOT complete until
git pushsucceeds - 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:
- The WordPress plugin — repo root,
includes/,vendor/. Ships towp-content/plugins/. Installed per engagement and uninstalled at the end. - The central history server (
server/) — a standalone PHP app deployed to a VPS..gitattributesmarksserver/,.beads/,.claude/,AGENTS.md,CLAUDE.md, andsteps.mdasexport-ignore, so Gitea'sarchive/main.zip(which is the plugin release artifact) excludes them. Any new non-plugin file at the repo root needs anexport-ignoreline.
Plugin
att-site-healthcheck.php— header, constants, requires, step discovery onplugins_loaded, PUC update checker wiring.includes/steps/<order>-<slug>.php— one file per healthcheck step, eachreturning anATT_HC_Stepsubclass instance. Adding a step is dropping a file; reordering is renaming the numeric prefix (loaded innatsortorder); removing is deleting the file.ATT_HC_Stepsis the singleton registry and applies theatt_hc_stepsfilter after discovery.ATT_HC_Session— the server is the source of truth. Theatt_hc_sessionWP 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_Api—wp_remote_requestclient; every method throwsATT_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 companionwp-site-recoveryplugin (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.php
→ Router → Controllers/. 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*isrequiresAuth: falseat 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 SQLite —
config.php.exampledefaults to MySQL becausepdo_sqliteis missing on some Ubuntu + Sury PHP combinations. Keepmigrations/*.sqlportable across both (VARCHAR(N),BIGINT, noAUTO_INCREMENT); driver-specific DDL needs dispatch inMigrations.php.
Conventions & Patterns
PHP version split — the easiest mistake to make here
- Plugin code (root +
includes/) must parse on PHP 7.4. Nostr_starts_with/str_contains/str_ends_with, no named arguments, nomatch, no nullsafe?->, no constructor promotion, no enums. This has been broken by drift before (commit3c64dd8) — lint against 7.4, don't just eyeball it. - Server code (
server/) targets PHP 8.1+ and freely usesdeclare(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 withcurrent_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 namespaceAttHc\Server\.... - All SQL lives in
Store, always via prepared statements. - Request input goes through
Validate::*; responses throughHttp::json/Http::error({"error":…,"code":…}). - No composer. If you think you need a dependency, vendor it or don't.
server/config.phpis 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 —ghand PR workflows don't apply. Work lands onmain. - Release = bump
Version:in the plugin header andATT_HC_VERSIONandversion+last_updatedinupdates.json, then pushmain. PUC pollsupdates.jsonand installsarchive/main.zip, so whatever is onmainis what ships. Built zips are gitignored. steps.mdis 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.