Continuous health + SEO regression monitor for plexicus.ai.
A single-binary Bun/TypeScript tool that crawls the production site, runs 9 checks against it, and opens a GitHub issue when something breaks. The killer feature is sitemap-diff: any URL that existed in the committed reference snapshot but is missing from the current live sitemap is flagged as a likely SEO regression.
When plexicus/web was migrated to plexicus/web-new (this Astro rebuild), there's a real risk that some indexed URLs no longer exist on the new site — those pages 404, Google de-indexes them, and ranking drops. web-health catches that regression at the moment of deploy, not weeks later when traffic falls off a cliff.
| Check | What it does | Severity model |
|---|---|---|
status |
HTTP GET every URL in the live sitemap (~5,400 URLs). | error if any 4xx/5xx · warning if response > 3s |
sitemap-diff |
Compare current sitemap to reference/urls-snapshot.txt. |
error for every URL present in reference but missing from live |
redirects |
Audit the redirect chain captured during the status scan. | error on loops · warning on chains > 3 hops |
seo-meta |
Title, meta description, canonical, og:image on priority pages. | error on missing title · warning on the rest |
images |
<img> alt-text coverage on priority pages. |
warning if any image is missing an alt attribute |
internal-links |
Every internal <a href> on priority pages resolves to 200. |
error on broken internal link |
external-links |
HEAD-check every outbound <a href>. |
warning on 4xx/5xx · info on connection failure |
security-headers |
Presence of HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, CSP on the homepage. | warning per missing header |
accessibility |
Playwright + axe-core scan on priority pages (opt-in, slow). | error on critical axe violations · warning on serious |
Priority pages are listed in src/config.ts — the deeper checks (seo-meta, images, internal-links, external-links, accessibility) run only on those. Status + redirect + sitemap-diff run on the full sitemap.
bun install
# First-time setup: snapshot the live sitemap as the reference baseline.
bun run snapshot
# Run all checks (everything except accessibility).
bun run check
# Include accessibility (Playwright + axe — adds ~1–2 minutes).
bun run check --with-a11y
# Run only specific checkers.
bun run check --only=status,sitemap-diff
# Target a different host (e.g. staging).
bun run check --target=https://new-web.plexicus.aiThe CLI exits with 1 when any check produces an error finding, 0 otherwise. GitHub Actions reads this exit code to decide whether to open the regression issue.
.github/workflows/health-check.yml runs:
- Daily at 02:00 UTC — all checks except accessibility.
- Weekly Sunday 03:00 UTC — full run including Playwright + axe.
- On-demand via
workflow_dispatchwith optionalwith_a11yandtargetinputs. - Triggered by deploys of
plexicus/web-newviarepository_dispatchevent typeweb-new-deployed(see "Wiring up deploy triggers" below).
When a run fails it opens (or updates) a single issue labelled health-regression. When the next run passes, that issue auto-closes. The full JSON + Markdown reports are uploaded as workflow artifacts and retained for 30 days.
Add this step to plexicus/web-new's deploy workflow, after the GitHub Pages deploy succeeds:
- name: Trigger health-health check
env:
GH_TOKEN: ${{ secrets.WEB_HEALTH_DISPATCH_TOKEN }} # PAT with `repo` scope on plexicus/web-health
run: |
gh api -X POST /repos/plexicus/web-health/dispatches \
-f event_type=web-new-deployedThe reference URL list (reference/urls-snapshot.txt) is committed to the repo and represents the "URLs that must keep working". Refresh it intentionally — typically when:
- A URL was migrated to a new path and you've added a 301 redirect for the old one.
- An obsolete page was retired with sign-off from SEO.
- A new section of the site goes live and the URLs are now part of the canonical set.
bun run snapshot --target=https://www.plexicus.ai
git add reference/
git commit -m "snapshot: refresh URL reference after planned migration of /old-path"Every commit that updates reference/urls-snapshot.txt should explain why in the message — it changes the contract the tool enforces.
src/
index.ts # CLI (commander)
config.ts # Target, priority paths, thresholds, security headers
types.ts # Shared types
crawler.ts # Parallel HTTP fetcher with manual redirect tracking
sitemap.ts # Sitemap-index expansion (handles Astro's per-bucket layout)
checkers/
status.ts # HTTP status + response time
sitemap-diff.ts # vs. reference snapshot
redirects.ts # Chain depth + loop detection
seo-meta.ts # title / description / canonical / og:image
images.ts # alt text coverage
internal-links.ts # Recursive internal link crawl
external-links.ts # HEAD check on outbound links
security-headers.ts # HSTS, X-Frame-Options, CSP, etc.
accessibility.ts # Playwright + axe-core
reporter/
console.ts # Terminal output (chalk)
markdown.ts # GH-issue-ready summary
json.ts # Full machine-readable report
reference/
sitemap-snapshot.xml # Raw committed sitemap-index XML
urls-snapshot.txt # Parsed sorted URL list (one per line)
.github/workflows/
health-check.yml # Cron + dispatch + issue management
MIT