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.
This commit is contained in:
2026-07-23 09:09:11 +01:00
parent d4f643528d
commit f551b64e2e
2 changed files with 128 additions and 6 deletions

View File

@@ -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.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.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":"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}."}

133
CLAUDE.md
View File

@@ -52,18 +52,139 @@ bd close <id> # Complete work
## Build & Test ## 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 ```bash
# Example: # Plugin — must parse on PHP 7.4
# npm install find . -path ./vendor -prune -o -path ./server -prune -o -name '*.php' -print \
# npm test | 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 ## 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/<order>-<slug>.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 <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 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 ## 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.