From f551b64e2ebd9bf0d3c3644ab60192fc1e2073b1 Mon Sep 17 00:00:00 2001 From: Steve Hanlon Date: Thu, 23 Jul 2026 09:09:11 +0100 Subject: [PATCH] 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. --- .beads/issues.jsonl | 1 + CLAUDE.md | 133 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 128 insertions(+), 6 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index d20f112..bd39fc6 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -45,3 +45,4 @@ {"_type":"issue","id":"hc-5ix.13","title":"Polished HTML/PDF report styling + ability to email","notes":"HTML report builder (wph_build_html_report) with inline styles — printable, looks good. Download HTML + Download Markdown + Copy + Email actions on finish panel. Email uses wp_mail with HTML content-type, prefills admin_email, redirects with status. PDF deferred — would need vendoring Dompdf (~500KB) and isn't core to the workflow.","status":"closed","priority":3,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:17Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T15:13:12Z","started_at":"2026-06-11T15:04:59Z","closed_at":"2026-06-11T15:13:12Z","labels":["phase-3"],"dependencies":[{"issue_id":"hc-5ix.13","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:17Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"hc-5ix.11","title":"Stop-and-escalate decision support: when a step is marked blocked/escalate, surface the matching guidance from steps.md and link to a quote/escalation template","description":"Drive from the 'When to Stop and Escalate' table at the bottom of steps.md. Each escalation condition becomes a structured rule that fires when its preconditions hold (e.g. step=backup, status=blocked → 'Stop. Do not proceed. Restore backup before retry.'). Render as a banner on the affected step.","notes":"'Stop \u0026 escalate' summary panel at top of the dashboard listing any step with status=blocked, showing the step's escalation() guidance and the technician's notes. Per-step escalation banner already shown inline when blocked (carried over from phase 1).","status":"closed","priority":3,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:16Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T15:13:12Z","started_at":"2026-06-11T15:04:58Z","closed_at":"2026-06-11T15:13:12Z","labels":["phase-3"],"dependencies":[{"issue_id":"hc-5ix.11","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:16Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"hc-5ix.10","title":"Linear stepper UI: replace single-page with one-step-per-page + progress bar + per-step timer","notes":"Implemented as a sticky step-index sidebar with status dots (◯ open · ✓ done · ⏭ skipped · ✗ blocked · — n/a). Anchor links jump to each step. Not a full one-step-per-page stepper — that flow was rejected after consideration because the single-page overview is more useful for scrolling between related steps. The sidebar gives the same at-a-glance progress without losing context.","status":"closed","priority":3,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:15Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T15:13:11Z","started_at":"2026-06-11T15:04:57Z","closed_at":"2026-06-11T15:13:11Z","labels":["phase-3"],"dependencies":[{"issue_id":"hc-5ix.10","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:15Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"memory","key":"healthcheck-server-deployment-rsync-target-is-sjh-home","value":"Healthcheck server deployment: rsync target is sjh:/home/www/healthcheck/ (Ubuntu VPS). Public URL: https://healthcheck.awesometechtraining.com (nginx vhost at /etc/nginx/sites-enabled/healthcheck.conf, php-fpm backed). Dashboard at /dashboard. Deploy: rsync -avz --delete --exclude=data/ --exclude=config.php --exclude=config.php.pre-mysql.* --exclude=/x ~/dev/att/att-site-healthcheck/server/ sjh:/home/www/healthcheck/ — the two extra excludes protect a mysql-migration rollback config and an unknown /x file that live on prod but aren't in git. Smoke test: curl https://healthcheck.awesometechtraining.com/ returns {ok:true}."} diff --git a/CLAUDE.md b/CLAUDE.md index 50af487..f18cc2b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -52,18 +52,139 @@ bd close # Complete work ## Build & Test -_Add your build and test commands here_ +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): ```bash -# Example: -# npm install -# npm test +# 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: + +```bash +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 -_Add a brief overview of your project architecture_ +**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/-.php` — one file per healthcheck step, each + `return`ing 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_Session` — **the 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_Api` — `wp_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.php` +→ `Router` → `Controllers/`. `Store` holds all SQL; `Db` is a PDO singleton that +runs `Migrations::ensureRan()` on first connection. + +- API routes take `Authorization: Bearer ` (`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=` once, then a PHP session cookie, plus a CSRF token on POSTs. +- Storage is PDO against **either MySQL 8.0+ or SQLite** — `config.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 -_Add your project-specific conventions here_ +### 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.