This project is not covered by Drupal’s security advisory policy.

Site Doctor helps you answer a simple question that gets harder as a Drupal site ages: "is my site healthy — and is it getting better or worse?"

It runs read-only diagnostic checks (pending updates, configuration drift, permission risks, dormant accounts, unused leftovers) on a schedule and — unlike a one-time audit — remembers what it finds: every finding carries a stable fingerprint, so the built-in report shows what appeared this week, what stopped being found, what got worse, and what has been quietly degrading for months. It is not a database/query monitor (see Site Health) and not a per-request developer profiler (see Webprofiler) — Site Doctor tracks site-level diagnostic findings over time.

Every result is a projection of the same structured schema: a plain-language report page with history and trends; CLI commands on both Drupal core's new dr CLI and Drush, with CI-friendly output and exit codes for your deployment pipeline; and machine-readable output that automation and AI agents can consume. Site Doctor never mutates site configuration or content — it writes only to its own reporting tables, with configurable retention. There is no auto-fix: it diagnoses and reports; fixing remains a deliberate human decision.

Features

The report page and results store (new in 1.0.0-alpha2):

  • Scheduled check-ups via cron (frequency configurable, default daily); CLI runs are stored too, with a per-run opt-out.
  • The report at /admin/reports/site-doctor, in plain language: current problems with an age breakdown, a filterable "what changed" timeline (new / no longer found / got worse / improved, with fix guidance), severity trends charted over time, and an Inventory — facts on file about the site (every permission grant, configuration overrides, the declared workflow) with their own change history, kept apart from problems. Every filtered view is a bookmarkable URL.
  • Honest history: disabling a check is recorded, never silent — frozen findings say why and since when; a category-filtered run declares itself partial and can never fabricate "resolved".
  • Configurable retention with the horizons printed on the views they bound; the store monitors its own purging as a check like any other.

Checks:

  • Pending updates (system) — pending database/post-deployment updates (code deployed ahead of the database, or a deploy script that swallowed a failure) and mismatched entity/field definitions — the chronic class that drush updb does not fix.
  • Maintenance mode (system) — the site was left in maintenance mode.
  • Permission risk audit (security) — permissions flagged "restrict access" granted to the anonymous role (Error) or to authenticated/other non-admin roles (Warning); admin roles are exempt by contract.
  • Permission grants inventory (security, new in alpha2) — the full (role, permission) inventory as memory rather than judgment: the report's Inventory answers "when did this role gain that permission?" with a stored date, for every grant — not only the risky ones. Never gates CI.
  • Dormant privileged accounts (security) — active accounts holding admin or restricted-permission roles with no login past a configurable threshold (default 180 days); never-logged-in accounts included.
  • Configuration drift classifier (config) — compares active configuration against your sync directory and frames each difference by consequence: what the next config:import would delete, revert, or re-create. Severity follows your declared workflow (sync, active, or auto): sync-disciplined sites get CI-gating Warnings; recipe-era and site-builder sites that treat active storage as the source of truth get a never-nagging Info inventory instead. Also aggregates all runtime config overrides (settings.php / modules) site-wide, reporting the overridden key paths — never the values, which is where secrets live.
  • Entropy checks (advisory — never gate CI) — unused image styles, orphaned view modes, roles with no users, unused text formats.
  • Results store health (new in alpha2) — Site Doctor applies its own machinery to itself: a stalled retention purge is a Warning like any other finding.
  • Extensible by design: drop a check plugin in any module — site-specific business rules become first-class findings on every surface, history included. See CONTRIBUTING.md and AGENTS.md in the repository.

Query commands (answers, not checks):

  • site-doctor:who-can PERMISSION — which roles hold a permission and through which path (explicit grant, admin role, user-1 super-user policy), with user counts — the questions ad-hoc reasoning (human or LLM) reliably gets wrong.
  • site-doctor:role-diff ROLE_A ROLE_B — compare the explicit permission sets of two roles.
  • Roadmap: read commands over the stored history ("what changed since last week?" from the CLI), and pluggable queries — define a site-specific lookup as one Query plugin and it derives a CLI command on both dr and Drush and a typed MCP tool, giving AI agents safe, bounded answers about your site instead of improvised database queries.

Checks (roadmap):

  • Cron and queue health (a stuck queue with 40k items is invisible today).
  • Dangling entity reference auditor (references to deleted targets, media pointing at missing files).
  • Extension risk report (release age, security coverage, unused-module signals).
  • Route latency sampler (production-safe p50/p95 per route, week-over-week regressions — zero external infrastructure).
  • Runtime deprecation telemetry (which deprecated code paths actually execute in production, ranked by hit count — prioritize your next major upgrade by real impact).
  • Content model export (Mermaid/JSON description of your bundles, fields, and references — documentation and agent context).

Surfaces:

  • The report page at /admin/reports/site-doctor and a settings form at /admin/config/development/site-doctor, each behind its own restrict-flagged permission.
  • CLI on both Drupal core's dr (11.4+) and Drush 13+ from one implementation: site-doctor:check [category] --format=table|json|junit|llm. Exit codes are a frozen contract: 0 ok/info, 1 warnings, 2 errors, 3 the diagnosis itself failed (a crashed check never aborts the run — the rest still report). Machine envelopes declare their scope: a category-filtered run can never be mistaken for full coverage.
  • --format=llm — self-describing, bounded output for AI agents: embedded severity semantics and exit-code map, truncation markers on long evidence, staleness fields on every result.
  • Status report summary line at /admin/reports/status, including collection staleness.
  • Tool API integration (roadmap): each check and query exposed as a tool so AI agents and MCP clients can query site health instead of guessing.

Use cases:

  • Pre-deployment CI gate ("fail the pipeline on dangerous config drift"), post-deploy verification ("did updates actually run?"), monthly maintenance review with the "what changed" timeline, incident forensics ("when did that role gain that permission?"), upgrade planning, security/permission audits, handover audits of inherited sites, and giving AI agents accurate structured facts about the site.
  • And the part only your own code can supply: site-specific checks encode your business rules ("every product needs a hero image", "this role must never gain that permission") as one small plugin class in your custom module, and it automatically gets every surface: both CLIs, all output formats, CI exit codes that can gate your deployments, and the full trend history. Agencies can ship one internal check pack across every client site.

Post-Installation

Enable the module, then:

  1. Run drush site-doctor:check (or dr site-doctor:check on Drupal 11.4+) for the first check-up, and open /admin/reports/site-doctor. With cron running, check-ups collect on a schedule from here on (default daily).
  2. Visit /admin/config/development/site-doctor to declare your configuration workflow (so drift severity matches your reality), set the check-up frequency and retention, and switch off any checks you don't want — switching off is always recorded, never silent.
  3. For CI: run drush site-doctor:check --format=junit in your pipeline and gate on the exit code.

No content types, fields, or front-end output are created. All checks are read-only toward your site: Site Doctor writes only to its own four reporting tables, and retention keeps them bounded.

Additional Requirements

None beyond Drupal core (Drupal 11, PHP 8.3+). Drush 13+ is optional — core's dr CLI (11.4+) is fully supported. Individual checks degrade gracefully: a check whose subject doesn't exist on your site reports "not applicable", never a false pass.

Site Doctor deliberately does not duplicate established tools — it complements them and links to them from findings where relevant:

  • Upgrade Status — static deprecation analysis (Site Doctor's runtime telemetry will tell you which of those findings matter in production).
  • Security Review — security best-practice checklist (Site Doctor's security category covers what a checklist can't: reverse lookups, grant-path analysis, account dormancy).
  • Linkchecker — external/content link checking (Site Doctor will cover entity-reference integrity only).
  • Site Audit — use it today for one-shot scored audit reports; see also Similar projects below.
  • Tool API / MCP Server — enables the agent-facing tool surface (roadmap).

Similar projects

  • Site Audit — point-in-time static best-practice checks via Drush. Site Doctor differs in its design around recorded history/trends (in development), production runtime telemetry (roadmap), and results exposed to CI and AI agents (available now: severity-mapped exit codes, junit, and an agent-oriented output format).
  • Healthcheck — similar plugin-based concept from the Drupal 8 era, including historical reports. Site Doctor is a Drupal 11-native take with runtime telemetry and an agent/automation surface.
  • Webprofiler — per-request developer profiling: deep, dev-only, heavyweight. Site Doctor's roadmap latency sampler is the inverse instrument: production-safe 1-in-N sampling, p50/p95 per route trended over time. The sampler finds which route regressed; webprofiler dissects why, in dev.
  • Site Health — database query monitoring (slow queries, per-statement statistics). Site Doctor is route- and site-level over time, not a query profiler.
  • Monitoring — sensor integration with external monitoring stacks (Icinga, etc.). Site Doctor is self-contained with no external infrastructure.
  • health_check / health_check_url and similar — uptime ping endpoints for load balancers; a different problem entirely.

Supporting this Module

Issues, reviews, and merge requests are welcome — the check plugin API is
designed so a new check is a small, self-contained contribution: one plugin
class implementing collect(), one kernel test extending the provided
CheckKernelTestBase, and it automatically appears on every surface.

Community Documentation

The README ships with the module: usage on both CLIs, CI integration with
exit-code semantics, configuration guide (including how to choose your
config workflow), and a writing-your-own-check guide. GitHub Actions
examples and a fuller tutorial are planned as the module matures.

AI-assisted development disclosure: this module is developed with AI
coding assistance under human direction and review. All code is
human-reviewed before commit; every check ships with kernel tests that
encode its expected behavior, and CI (phpcs, phpstan, phpunit, cspell,
code coverage) gates every merge request.

Project information

Releases