Back to the catalog

compose-doctor

Deterministic 0-100 health score and agent fix-loop for Android Jetpack Compose, wrapping detekt + compose-rules. Provides the /compose-doct

Open source Open in the app JSON README (API)

About

Deterministic 0-100 health score and agent fix-loop for Android Jetpack Compose, wrapping detekt + compose-rules. Provides the /compose-doctor command and AGENTS.md context.

Details

Kind
Plugins
Topic
Government & public data
Publisher
rotemmiz
Origin
gemini
Category
ferramentas
Version
0.2.0
Stars
5
Last push
2026-08-28T13:08:30Z
Repository state
ativo
Language
Kotlin
License
MIT
Added
2026-08-30 14:13:39
Updated
2026-08-30 14:13:39
Origin id
rotemmiz/compose-doctor

README

# compose-doctor

[![Gradle Plugin Portal](https://img.shields.io/gradle-plugin-portal/v/dev.composedoctor?color=0ea5a4&logo=gradle)](https://plugins.gradle.org/plugin/dev.composedoctor)
[![Claude Code Plugin](https://img.shields.io/badge/Claude%20Code-Plugin-7c3aed?logo=anthropic)](https://github.com/rotemmiz/compose-doctor)
[![Gemini CLI Extension](https://img.shields.io/badge/Gemini%20CLI-Extension-4285f4?logo=google)](https://github.com/rotemmiz/compose-doctor)
[![ci](https://github.com/rotemmiz/compose-doctor/actions/workflows/ci.yml/badge.svg)](https://github.com/rotemmiz/compose-doctor/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Website](https://img.shields.io/badge/web-composedoctor.dev-0ea5a4)](https://composedoctor.dev)

**A deterministic health check for Android Jetpack Compose — the [React Doctor](https://www.react.doctor/) idea, for Compose.**

Your agent writes Compose; this scores it. compose-doctor runs [detekt](https://detekt.dev/) +
[compose-rules](https://mrmans0n.github.io/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](https://plugins.gradle.org/plugin/dev.composedoctor)
> as `dev.composedoctor` — apply it with `plugins { id("dev.composedoctor") version "0.1.0" }`
> (see [Use it in your project](#use-it-in-your-project)).

## Why

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.

## The score

```
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](docs/RULES.md).

## Try it

The repo ships a deliberately-flawed [`playground/`](playground) feed app, wired to the plugin from
source via a composite build. With JDK 21:

```bash
git clone git@github.com:rotemmiz/compose-doctor.git && cd compose-doctor
./gradlew -p playground composeDoctor
```

> 📖 [docs/TRY-IT-PLAYGROUND.md](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:

  ```json
  {
    "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.

## Use it in your project

The plugin is on the [Gradle Plugin Portal](https://plugins.gradle.org/plugin/dev.composedoctor) —
apply it by id and pin the version (pinning keeps scores comparable across runs):

```kotlin
// 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()`, and `google()`
> available — the plugin pulls detekt and the `io.nlopez.compose.rules` ruleset 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 `:detekt` task — pin Gradle's JVM (`org.gradle.java.home`) if your
> default JDK is newer.

## CI

A reusable workflow posts the score on every PR, uploads SARIF for code-scanning annotations, and
gates on a threshold:

```yaml
# .github/workflows/health.yml
jobs:
  health:
    uses: rotemmiz/compose-doctor/.github/workflows/compose-doctor.yml@main
    with:
      gradle-args: composeDoctor
      fail-below: 75
```

It 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:

![compose-doctor's sticky PR score comment](docs/img/pr-score-comment.png)

### Repo Health Badge

Showcase your project's Compose health score in your README:

```markdown
[![Compose Doctor](https://img.shields.io/badge/Compose%20Doctor-75%2B%20Great-10b981?logo=android)](https://github.com/rotemmiz/compose-doctor)
```

## Agent skill

[`skills/compose-doctor/SKILL.md`](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):

```text
/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`](AGENTS.md), the neutral mirror of the skill. OpenCode also ships the `/compose-doctor`
command in-repo at [`.opencode/commands/`](.opencode/commands).

**Gemini CLI** — install as an extension (bundles the command + `AGENTS.md` context):

```bash
gemini extensions install https://github.com/rotemmiz/compose-doctor
```

See [`skills/README.md`](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`](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](docs/AGENT-HARNESS.md).

## How it works

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.

## Roadmap

- ~~Publish to the Gradle Plugin Portal.~~ ✅ live as [`dev.composedoctor`](https://plugins.gradle.org/plugin/dev.composedoctor).
- Wire **android-lint** to populate the Security/Accessibility dimensions.
- `composeDoctorBaseline` task to seed detekt's `baseline.xml`.

## License

[MIT](LICENSE) © 2026 Rotem Meidan

More