{
  "markdown": "# compose-doctor\n\n[![Gradle Plugin Portal](https://img.shields.io/gradle-plugin-portal/v/dev.composedoctor?color=0ea5a4&logo=gradle)](https://plugins.gradle.org/plugin/dev.composedoctor)\n[![Claude Code Plugin](https://img.shields.io/badge/Claude%20Code-Plugin-7c3aed?logo=anthropic)](https://github.com/rotemmiz/compose-doctor)\n[![Gemini CLI Extension](https://img.shields.io/badge/Gemini%20CLI-Extension-4285f4?logo=google)](https://github.com/rotemmiz/compose-doctor)\n[![ci](https://github.com/rotemmiz/compose-doctor/actions/workflows/ci.yml/badge.svg)](https://github.com/rotemmiz/compose-doctor/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![Website](https://img.shields.io/badge/web-composedoctor.dev-0ea5a4)](https://composedoctor.dev)\n\n**A deterministic health check for Android Jetpack Compose — the [React Doctor](https://www.react.doctor/) idea, for Compose.**\n\nYour agent writes Compose; this scores it. compose-doctor runs [detekt](https://detekt.dev/) +\n[compose-rules](https://mrmans0n.github.io/compose-rules/) under the hood, then turns the findings\ninto a single **0–100 health score**, a structured report an agent can fix against, and a CI/PR gate.\n\n> ✅ **Published.** The plugin is live on the [Gradle Plugin Portal](https://plugins.gradle.org/plugin/dev.composedoctor)\n> as `dev.composedoctor` — apply it with `plugins { id(\"dev.composedoctor\") version \"0.1.0\" }`\n> (see [Use it in your project](#use-it-in-your-project)).\n\n## Why\n\nThe pieces exist in the Compose world but are unbundled — `compose-rules`/`compose-lints` for the\nrules, scattered agent guides, and no shared score. compose-doctor's value isn't the rules (those\nare battle-tested upstream); it's the **bundle**: one score, an agent fix-loop, and a CI gate.\n\n## The score\n\n```\nscore = 100 − (uniqueErrorRules × 1.5) − (uniqueWarningRules × 0.75)      // clamped to [0, 100]\n```\n\nTranslated directly from React Doctor: the unit is **unique rule IDs triggered**, not instance\ncount and not normalized by code size. Fixing 49 of 50 violations of a rule does not move the\nscore; clearing the **last** one removes that rule's penalty. That makes the score deterministic\nwithout calibration — and makes the agent loop (\"clear one rule at a time\") effective.\n\nLabels: **75+ Great · 50–74 Needs work · <50 Critical**.\n\nFindings are grouped into display **dimensions** (State/Correctness, Performance, Architecture,\nSecurity, Accessibility) for the report — dimensions do not weight the overall score.\n\ncompose-doctor applies a curated policy on top of detekt + compose-rules — Compose health plus\ngenuine bugs, with a two-tier severity (errors −1.5, warnings −0.75) and style noise disabled. See\n[docs/RULES.md](docs/RULES.md).\n\n## Try it\n\nThe repo ships a deliberately-flawed [`playground/`](playground) feed app, wired to the plugin from\nsource via a composite build. With JDK 21:\n\n```bash\ngit clone git@github.com:rotemmiz/compose-doctor.git && cd compose-doctor\n./gradlew -p playground composeDoctor\n```\n\n> 📖 [docs/TRY-IT-PLAYGROUND.md](docs/TRY-IT-PLAYGROUND.md) is a guided walkthrough — run it, read\n> the report, fix a rule, and watch the score move.\n\n```\ncompose-doctor — health score: 72/100  [NEEDS_WORK]\n  unique error rules:   9\n  unique warning rules: 19\n  total findings:       34\n\n  by dimension:\n    ARCHITECTURE       82/100\n    STATE_CORRECTNESS  94/100\n    ...\n```\n\nOutputs:\n- `build/reports/compose-doctor/score.json` — machine-readable score + findings (for agents/CI). Excerpt:\n\n  ```json\n  {\n    \"schemaVersion\": 1, \"status\": \"ok\", \"score\": 72, \"label\": \"NEEDS_WORK\",\n    \"uniqueErrorRules\": 9, \"uniqueWarningRules\": 19, \"totalFindings\": 34,\n    \"dimensions\": { \"ARCHITECTURE\": 82, \"PERFORMANCE\": 96, \"STATE_CORRECTNESS\": 94 },\n    \"byRule\": [\n      { \"ruleId\": \"CompositionLocalAllowlist\", \"severity\": \"ERROR\", \"count\": 1,\n        \"scoreImpactIfCleared\": 1.5, \"fixHint\": \"Avoid this CompositionLocal or add it to the allowlist.\" }\n    ]\n  }\n  ```\n- `build/reports/detekt/detekt.sarif` — findings in SARIF, with precise locations.\n\n## Use it in your project\n\nThe plugin is on the [Gradle Plugin Portal](https://plugins.gradle.org/plugin/dev.composedoctor) —\napply it by id and pin the version (pinning keeps scores comparable across runs):\n\n```kotlin\n// build.gradle.kts of a module with Compose source\nplugins {\n    id(\"dev.composedoctor\") version \"0.1.0\"\n}\n\ncomposeDoctor {\n    failBelow.set(75)          // fail the build below this score (optional)\n    // per-engine strictness — see docs/RULES.md (needs: import dev.composedoctor.plugin.EngineLevel)\n    // detekt  = EngineLevel.ERRORS              // count only detekt's genuine bugs\n    // compose = EngineLevel.ERRORS_AND_WARNINGS // count all Compose issues (default)\n    // autoConfigureDetekt.set(false)            // if you already configure detekt yourself\n}\n```\n\nThen `./gradlew composeDoctor`. The plugin applies detekt, attaches the compose-rules ruleset\n(config bundled), enables SARIF, and scores it. Existing detekt machinery — `baseline.xml`,\n`detekt.yml`, `@Suppress` — applies as usual.\n\n> **Repositories:** your build needs `gradlePluginPortal()`, `mavenCentral()`, and `google()`\n> available — the plugin pulls detekt and the `io.nlopez.compose.rules` ruleset from them.\n>\n> **JDK:** run Gradle on **JDK 17–21**. detekt (1.23.x) can't analyze under a JDK newer than 22, so\n> a daemon on JDK 22+ fails the `:detekt` task — pin Gradle's JVM (`org.gradle.java.home`) if your\n> default JDK is newer.\n\n## CI\n\nA reusable workflow posts the score on every PR, uploads SARIF for code-scanning annotations, and\ngates on a threshold:\n\n```yaml\n# .github/workflows/health.yml\njobs:\n  health:\n    uses: rotemmiz/compose-doctor/.github/workflows/compose-doctor.yml@main\n    with:\n      gradle-args: composeDoctor\n      fail-below: 75\n```\n\nIt posts a sticky score comment on the PR — a health badge, the headline score, the top outstanding\nfindings (with file:line and a fix hint), and a per-dimension breakdown:\n\n![compose-doctor's sticky PR score comment](docs/img/pr-score-comment.png)\n\n### Repo Health Badge\n\nShowcase your project's Compose health score in your README:\n\n```markdown\n[![Compose Doctor](https://img.shields.io/badge/Compose%20Doctor-75%2B%20Great-10b981?logo=android)](https://github.com/rotemmiz/compose-doctor)\n```\n\n## Agent skill\n\n[`skills/compose-doctor/SKILL.md`](skills/compose-doctor/SKILL.md) teaches a coding agent to run the\ntask, read the SARIF, and fix the highest-value rule iteratively — plus Compose best-practices to\navoid the findings up front. It's the single source of truth; the per-agent packaging below wraps it.\n\n**Claude Code — install as a plugin** (bundles the skill + a `/compose-doctor` command):\n\n```text\n/plugin marketplace add rotemmiz/compose-doctor\n/plugin install compose-doctor@compose-doctor\n```\n\n**Codex · OpenCode · Google Antigravity · Cursor** — zero install: all auto-read the root\n[`AGENTS.md`](AGENTS.md), the neutral mirror of the skill. OpenCode also ships the `/compose-doctor`\ncommand in-repo at [`.opencode/commands/`](.opencode/commands).\n\n**Gemini CLI** — install as an extension (bundles the command + `AGENTS.md` context):\n\n```bash\ngemini extensions install https://github.com/rotemmiz/compose-doctor\n```\n\nSee [`skills/README.md`](skills/README.md) for all install paths. The skill is **self-bootstrapping**:\nif a module has no `composeDoctor` task yet, it runs the bundled\n[`init/compose-doctor.init.gradle.kts`](init/compose-doctor.init.gradle.kts) to apply the plugin\nwithout editing any build file.\n\nThe full agent loop, the `score.json` contract, and the memory/integrity model are specified in\n[docs/AGENT-HARNESS.md](docs/AGENT-HARNESS.md).\n\n## How it works\n\nA single self-contained Gradle plugin orchestrates the engines and aggregates their SARIF — it does\nnot embed detekt-core or reimplement rules. Internally: the `scoring` package is a pure,\ndeterministic function; `rulemap` maps rule IDs to dimensions; the plugin does the wiring, scoring,\nand reporting.\n\n## Roadmap\n\n- ~~Publish to the Gradle Plugin Portal.~~ ✅ live as [`dev.composedoctor`](https://plugins.gradle.org/plugin/dev.composedoctor).\n- Wire **android-lint** to populate the Security/Accessibility dimensions.\n- `composeDoctorBaseline` task to seed detekt's `baseline.xml`.\n\n## License\n\n[MIT](LICENSE) © 2026 Rotem Meidan\n",
  "bytes": 8395,
  "sha": "f1cff244a3e640880e2b7d13a208e7045a39d21b7115a70882069c5dc2a4e9c0",
  "repo_slug": "rotemmiz/compose-doctor",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/plg_rotemmiz_compose_doctor_dbb973f6/readme"
}