A deterministic health check for Android Jetpack Compose — the React Doctor idea, for Compose.
Your agent writes Compose; this scores it. compose-doctor runs detekt + compose-rules under the hood, then turns the findings into a single 0–100 health score, a structured report an agent can fix against, and a CI/PR gate.
✅ Published. The plugin is live on the Gradle Plugin Portal as
dev.composedoctor— apply it withplugins { id("dev.composedoctor") version "0.1.0" }(see Use it in your project).
The pieces exist in the Compose world but are unbundled — compose-rules/compose-lints for the
rules, scattered agent guides, and no shared score. compose-doctor's value isn't the rules (those
are battle-tested upstream); it's the bundle: one score, an agent fix-loop, and a CI gate.
score = 100 − (uniqueErrorRules × 1.5) − (uniqueWarningRules × 0.75) // clamped to [0, 100]
Translated directly from React Doctor: the unit is unique rule IDs triggered, not instance count and not normalized by code size. Fixing 49 of 50 violations of a rule does not move the score; clearing the last one removes that rule's penalty. That makes the score deterministic without calibration — and makes the agent loop ("clear one rule at a time") effective.
Labels: 75+ Great · 50–74 Needs work · <50 Critical.
Findings are grouped into display dimensions (State/Correctness, Performance, Architecture, Security, Accessibility) for the report — dimensions do not weight the overall score.
compose-doctor applies a curated policy on top of detekt + compose-rules — Compose health plus genuine bugs, with a two-tier severity (errors −1.5, warnings −0.75) and style noise disabled. See docs/RULES.md.
The repo ships a deliberately-flawed playground/ feed app, wired to the plugin from
source via a composite build. With JDK 21:
git clone git@github.com:rotemmiz/compose-doctor.git && cd compose-doctor
./gradlew -p playground composeDoctor📖 docs/TRY-IT-PLAYGROUND.md is a guided walkthrough — run it, read the report, fix a rule, and watch the score move.
compose-doctor — health score: 72/100 [NEEDS_WORK]
unique error rules: 9
unique warning rules: 19
total findings: 34
by dimension:
ARCHITECTURE 82/100
STATE_CORRECTNESS 94/100
...
Outputs:
-
build/reports/compose-doctor/score.json— machine-readable score + findings (for agents/CI). Excerpt:{ "schemaVersion": 1, "status": "ok", "score": 72, "label": "NEEDS_WORK", "uniqueErrorRules": 9, "uniqueWarningRules": 19, "totalFindings": 34, "dimensions": { "ARCHITECTURE": 82, "PERFORMANCE": 96, "STATE_CORRECTNESS": 94 }, "byRule": [ { "ruleId": "CompositionLocalAllowlist", "severity": "ERROR", "count": 1, "scoreImpactIfCleared": 1.5, "fixHint": "Avoid this CompositionLocal or add it to the allowlist." } ] } -
build/reports/detekt/detekt.sarif— findings in SARIF, with precise locations.
The plugin is on the Gradle Plugin Portal — apply it by id and pin the version (pinning keeps scores comparable across runs):
// build.gradle.kts of a module with Compose source
plugins {
id("dev.composedoctor") version "0.1.0"
}
composeDoctor {
failBelow.set(75) // fail the build below this score (optional)
// per-engine strictness — see docs/RULES.md (needs: import dev.composedoctor.plugin.EngineLevel)
// detekt = EngineLevel.ERRORS // count only detekt's genuine bugs
// compose = EngineLevel.ERRORS_AND_WARNINGS // count all Compose issues (default)
// autoConfigureDetekt.set(false) // if you already configure detekt yourself
}Then ./gradlew composeDoctor. The plugin applies detekt, attaches the compose-rules ruleset
(config bundled), enables SARIF, and scores it. Existing detekt machinery — baseline.xml,
detekt.yml, @Suppress — applies as usual.
Repositories: your build needs
gradlePluginPortal(),mavenCentral(), andgoogle()available — the plugin pulls detekt and theio.nlopez.compose.rulesruleset from them.JDK: run Gradle on JDK 17–21. detekt (1.23.x) can't analyze under a JDK newer than 22, so a daemon on JDK 22+ fails the
:detekttask — pin Gradle's JVM (org.gradle.java.home) if your default JDK is newer.
A reusable workflow posts the score on every PR, uploads SARIF for code-scanning annotations, and gates on a threshold:
# .github/workflows/health.yml
jobs:
health:
uses: rotemmiz/compose-doctor/.github/workflows/compose-doctor.yml@main
with:
gradle-args: composeDoctor
fail-below: 75It posts a sticky score comment on the PR — a health badge, the headline score, the top outstanding findings (with file:line and a fix hint), and a per-dimension breakdown:
Showcase your project's Compose health score in your README:
[](https://github.com/rotemmiz/compose-doctor)skills/compose-doctor/SKILL.md teaches a coding agent to run the
task, read the SARIF, and fix the highest-value rule iteratively — plus Compose best-practices to
avoid the findings up front. It's the single source of truth; the per-agent packaging below wraps it.
Claude Code — install as a plugin (bundles the skill + a /compose-doctor command):
/plugin marketplace add rotemmiz/compose-doctor
/plugin install compose-doctor@compose-doctor
Codex · OpenCode · Google Antigravity · Cursor — zero install: all auto-read the root
AGENTS.md, the neutral mirror of the skill. OpenCode also ships the /compose-doctor
command in-repo at .opencode/commands/.
Gemini CLI — install as an extension (bundles the command + AGENTS.md context):
gemini extensions install https://github.com/rotemmiz/compose-doctorSee skills/README.md for all install paths. The skill is self-bootstrapping:
if a module has no composeDoctor task yet, it runs the bundled
init/compose-doctor.init.gradle.kts to apply the plugin
without editing any build file.
The full agent loop, the score.json contract, and the memory/integrity model are specified in
docs/AGENT-HARNESS.md.
A single self-contained Gradle plugin orchestrates the engines and aggregates their SARIF — it does
not embed detekt-core or reimplement rules. Internally: the scoring package is a pure,
deterministic function; rulemap maps rule IDs to dimensions; the plugin does the wiring, scoring,
and reporting.
Publish to the Gradle Plugin Portal.✅ live asdev.composedoctor.- Wire android-lint to populate the Security/Accessibility dimensions.
composeDoctorBaselinetask to seed detekt'sbaseline.xml.
MIT © 2026 Rotem Meidan
