{"owner":"square","repo":"leakcanary","hasSkills":true,"totalSkillsCount":4,"totalTokensCount":3266,"categories":["root-instruction","mcp-config","marketplace","plugin-manifest"],"hasMcp":true,"mcpConfig":{"mcpServers":{"leakcanary":{"command":"npx","args":["-y","@modelcontextprotocol/server-leakcanary"]}}},"found":["AGENTS.md",".cursor/mcp.json",".claude-plugin/marketplace.json",".claude-plugin/plugin.json"],"skills":{"AGENTS.md":"# LeakCanary — agent guide\n\nA memory leak detection library for Android, plus Shark, the heap analyzer underneath it. Published\nto Maven Central and consumed by a very large number of apps, so **the public API and the bytecode\nlevel are contracts**, not implementation details.\n\nNot equally, though. The `leakcanary*` modules are what apps depend on directly, and their public API\nis the contract that matters most: breaking backward compatibility there is a last resort. `shark*` is\nused far less, so a breaking change is on the table there when it buys a meaningful improvement. In\nboth cases the ABI dump is what makes the break deliberate instead of a surprise, so propose it rather\nthan assuming it's fine.\n\nThis file records what an agent would get wrong from reading the source alone. Anything derivable by\nreading the code belongs in the code, not here — please keep it that way when editing.\n\n## Layout\n\n| Directory | What's in it |\n| --- | --- |\n| `shark/` | Heap dump parsing and analysis. Plain JVM, no Android dependency, except `shark-android` which adds Android specific reference readers and matchers. |\n| `object-watcher/` | Watches objects for retention. The lowest layer, usable on its own. |\n| `leakcanary/` | The Android library, plus `leakcanary-app*`, a standalone UI app that is not part of the library. |\n| `plumber/` | Fixes for known Android framework leaks, installed automatically. |\n| `samples/` | Sample app. |\n| `docs/` | The [documentation site](https://square.github.io/leakcanary/) content. |\n\nThe group directories (`shark/`, `leakcanary/`, …) hold no code of their own — only modules do.\n\n## Build and test\n\n`docs/dev-env.md` is the contributor setup guide — code style, local deployment, examples of the\nsynthetic heap dump DSL. Read it for anything this section doesn't cover.\n\nBuilt with **Java 17**, targeting **Java 8 bytecode** repo wide. Both matter: consumers still on\nJava 8 have to be able to use the artifacts.\n\n```bash\n./gradlew build                       # what CI runs\n./gradlew :shark:shark:test           # one module's unit tests\n./gradlew detekt                      # static analysis, also run by the pre-push hook\n./gradlew updateKotlinAbi             # after any public API change, see below\n./gradlew siteDokka                   # regenerate docs/api\n```\n\n`docs/api/` — the `siteDokka` output — is git ignored, not committed. The release process\nregenerates it just before publishing the site (see `docs/releasing.md`), so a public API change\nmeans updating the ABI dump and nothing else. If you do run `siteDokka`, don't edit what it writes;\nfix the KDoc in the source.\n\nInstrumentation tests need a device or emulator and only cover `leakcanary-android`,\n`leakcanary-android-core`, `leakcanary-android-instrumentation` and `leakcanary-android-test`. CI\nruns them on one emulator per major Android release, from the minSdk to the newest API level with a\nsystem image, so a change that only works on some API levels will fail there rather than locally.\n\n## Things that will bite you\n\n**Public API changes fail the build until the ABI dump is updated.** `checkKotlinAbi` compares the\npublic ABI against the committed `api/*.api` files and runs as part of `check`, so `./gradlew build`\ncatches it. When it fails, run `./gradlew updateKotlinAbi` and commit the changed `api/*.api` files —\nbut read the diff first, because an unintended ABI change is exactly what this is meant to catch.\nModules with no public API are exempt; they're listed in `modulesWithoutPublicApi` in the root\n`build.gradle.kts`.\n\n**There are two ABI validation mechanisms, deliberately sharing task names** so that one command\ncovers the whole repo: the Kotlin Gradle plugin's `abiValidation()` for JVM modules, and an\nequivalent pair of tasks hand-rolled in the root `build.gradle.kts` for Android library modules,\nwhich KGP doesn't support yet ([KT-83410](https://youtrack.jetbrains.com/issue/KT-83410)).\n\n**So use `checkKotlinAbi`/`updateKotlinAbi`, never `checkLegacyAbi`/`updateLegacyAbi`.** The `Legacy`\npair is what Kotlin's own ABI validation documentation calls these tasks, but here they come straight\nfrom KGP and therefore exist *only on the JVM modules* — they silently skip every Android library\nmodule, which is most of the published ones. A green `updateLegacyAbi` means less than half the repo\nwas covered.\n\n**Some dependency versions are deliberately old.** The `compileOnly` AndroidX versions in\n`gradle/libs.versions.toml` are pinned to the *lowest* version LeakCanary supports, so that apps\nresolve to their own newer version without needing a resolution strategy. The inline comments say\nwhich ones and why. Don't bump them to fix a warning.\n\n**`HprofRetainedHeapPerfTest` and `HprofIOPerfTest` freeze exact numbers** — bytes read, and memory\nretained at each analysis step, within a margin. A change to how the analysis allocates or reads will\nfail them. That's the point: they exist to make memory and I/O regressions visible. Investigate\nbefore adjusting the expected values, and say in the PR why the new number is correct.\n\n**detekt runs on pre-push and in CI**, config at `config/detekt-config.yml`. The hook installs itself\nvia the `assemble` and `clean` tasks, so a fresh clone gets it after the first build. Run `detekt`\nbefore pushing rather than discovering it at push time.\n\n**`gh pr merge --auto` does not wait for CI here — it merges on the spot.** Auto-merge is enabled on\nthe repo, but `main` is deliberately left unprotected, so there are no required status checks for\nauto-merge to gate on. GitHub sees a mergeable pull request with nothing to wait for and merges\nimmediately, exiting zero and printing nothing, which reads exactly like it armed. Nothing in the\nrepo will stop a merge while CI is red — `main` is open on purpose — so waiting for green is your\njob, not the platform's. Wait explicitly and let the exit code decide:\n\n```bash\ngh pr checks <number> --watch --fail-fast && gh pr merge <number> --merge\n```\n\n`gh pr checks` exits zero only once every check has passed, so the `&&` is what makes this safe;\n`--fail-fast` returns as soon as one fails instead of sitting through the rest. A run takes 9 to 13\nminutes, nearly all of it the emulator matrix, so start that command detached — a foreground call\nthat gives up at ten minutes will usually be killed just before the last emulator reports.\n\n## Changelog\n\nEntries go in `docs/changelog.md` under `## Unreleased`, each starting with one of the markers from\nthe legend at the top of that file. Pick the marker from what the change *is*, not from how big it\nfeels, and grep for a comparable existing entry rather than guessing.\n\n**💥 means a crash fix here, not a breaking change** — the opposite of the\n[gitmoji](https://gitmoji.dev/) convention, and the mistake that convention trips people into.\nReaching for 💥 because a change feels impactful tells readers a crash was fixed when nothing\ncrashed. Breaking changes are ⚠️; when one needs more than a bullet, write it as a\n`### Breaking change: <summary>` heading with prose.\n\n**The changelog is for changes that matter to the people consuming LeakCanary**, not a record of\nevery diff. Refactors, internal cleanups and test-only changes usually don't need an entry.\n\n**`docs/changelog.md` is the only changelog in this repo, and it covers the libraries.** Shark Dive is\nreleased separately and has **no changelog**, because it has had no release — so a change to\n`shark/shark-dive/` gets no entry anywhere, and **don't create a file for one**. There was one, and it\nwas a hundred unreleased bullets describing the app to nobody; `docs/releasing-shark-dive.md` has why it\nwent and when starting one would be right.\n\n## Conventions\n\n- When a function's parameters don't fit on one line, put **each on its own line** — the existing\n  code is consistent about this and detekt won't tell you.\n- Commit subjects are imperative and describe the change, e.g. \"Keep modules without a public API off\n  the documentation site\". Explain *why* in the body when it isn't obvious.\n- Don't leave test-only or unused code in the committed tree. If scaffolding was needed to get\n  somewhere, remove it before the PR lands.\n- **The prose conventions in this repo are for the repo, not for the screen.** The guides, the KDoc and\n  the `notes/` files are written at length on purpose — the reader is someone about to change the code,\n  and the cost of a paragraph there is nothing. What a *user interface* says is governed by the opposite\n  rule: a label is one or two words, and the paragraph explaining it lives somewhere the reader goes\n  looking for it. `shark/shark-dive/AGENTS.md` has that rule and the mechanism, and Shark Dive is the\n  only substantial UI in this repo, so it is the only place it applies.\n- Test heap dumps are built with the `dump { }` DSL from `shark-hprof-test` (see `docs/dev-env.md`)\n  rather than committed as binary fixtures. Never hand-assemble hprof bytes. For a large realistic\n  dump, drive a real JVM via `HotSpotDiagnosticMXBean.dumpHeap`.\n- Tests use JUnit 4 and AssertJ. Instrumentation tests depend on `libs.assertjCore.android` rather\n  than `libs.assertjCore`, because AssertJ 3.16 and up can't load on API 24 — the catalog comment\n  says why. So an assertion that a unit test can use may not compile in an instrumentation test.\n\n## Scoped guides\n\nSubdirectories may carry their own `AGENTS.md`, and the closest one to the file being edited wins,\nlike `.gitignore`. Prefer putting guidance in the narrowest place it applies over growing this file —\na module's quirks belong next to the module.\n\nClaude Code reads `CLAUDE.md` rather than `AGENTS.md`, so each `AGENTS.md` is paired with a\n`CLAUDE.md` containing `@AGENTS.md`. Add both when adding a scoped guide.\n",".cursor/mcp.json":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/square/leakcanary/HEAD/.cursor/mcp.json)",".claude-plugin/marketplace.json":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/square/leakcanary/HEAD/.claude-plugin/marketplace.json)",".claude-plugin/plugin.json":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/square/leakcanary/HEAD/.claude-plugin/plugin.json)"},"files":{"AGENTS.md":"# LeakCanary — agent guide\n\nA memory leak detection library for Android, plus Shark, the heap analyzer underneath it. Published\nto Maven Central and consumed by a very large number of apps, so **the public API and the bytecode\nlevel are contracts**, not implementation details.\n\nNot equally, though. The `leakcanary*` modules are what apps depend on directly, and their public API\nis the contract that matters most: breaking backward compatibility there is a last resort. `shark*` is\nused far less, so a breaking change is on the table there when it buys a meaningful improvement. In\nboth cases the ABI dump is what makes the break deliberate instead of a surprise, so propose it rather\nthan assuming it's fine.\n\nThis file records what an agent would get wrong from reading the source alone. Anything derivable by\nreading the code belongs in the code, not here — please keep it that way when editing.\n\n## Layout\n\n| Directory | What's in it |\n| --- | --- |\n| `shark/` | Heap dump parsing and analysis. Plain JVM, no Android dependency, except `shark-android` which adds Android specific reference readers and matchers. |\n| `object-watcher/` | Watches objects for retention. The lowest layer, usable on its own. |\n| `leakcanary/` | The Android library, plus `leakcanary-app*`, a standalone UI app that is not part of the library. |\n| `plumber/` | Fixes for known Android framework leaks, installed automatically. |\n| `samples/` | Sample app. |\n| `docs/` | The [documentation site](https://square.github.io/leakcanary/) content. |\n\nThe group directories (`shark/`, `leakcanary/`, …) hold no code of their own — only modules do.\n\n## Build and test\n\n`docs/dev-env.md` is the contributor setup guide — code style, local deployment, examples of the\nsynthetic heap dump DSL. Read it for anything this section doesn't cover.\n\nBuilt with **Java 17**, targeting **Java 8 bytecode** repo wide. Both matter: consumers still on\nJava 8 have to be able to use the artifacts.\n\n```bash\n./gradlew build                       # what CI runs\n./gradlew :shark:shark:test           # one module's unit tests\n./gradlew detekt                      # static analysis, also run by the pre-push hook\n./gradlew updateKotlinAbi             # after any public API change, see below\n./gradlew siteDokka                   # regenerate docs/api\n```\n\n`docs/api/` — the `siteDokka` output — is git ignored, not committed. The release process\nregenerates it just before publishing the site (see `docs/releasing.md`), so a public API change\nmeans updating the ABI dump and nothing else. If you do run `siteDokka`, don't edit what it writes;\nfix the KDoc in the source.\n\nInstrumentation tests need a device or emulator and only cover `leakcanary-android`,\n`leakcanary-android-core`, `leakcanary-android-instrumentation` and `leakcanary-android-test`. CI\nruns them on one emulator per major Android release, from the minSdk to the newest API level with a\nsystem image, so a change that only works on some API levels will fail there rather than locally.\n\n## Things that will bite you\n\n**Public API changes fail the build until the ABI dump is updated.** `checkKotlinAbi` compares the\npublic ABI against the committed `api/*.api` files and runs as part of `check`, so `./gradlew build`\ncatches it. When it fails, run `./gradlew updateKotlinAbi` and commit the changed `api/*.api` files —\nbut read the diff first, because an unintended ABI change is exactly what this is meant to catch.\nModules with no public API are exempt; they're listed in `modulesWithoutPublicApi` in the root\n`build.gradle.kts`.\n\n**There are two ABI validation mechanisms, deliberately sharing task names** so that one command\ncovers the whole repo: the Kotlin Gradle plugin's `abiValidation()` for JVM modules, and an\nequivalent pair of tasks hand-rolled in the root `build.gradle.kts` for Android library modules,\nwhich KGP doesn't support yet ([KT-83410](https://youtrack.jetbrains.com/issue/KT-83410)).\n\n**So use `checkKotlinAbi`/`updateKotlinAbi`, never `checkLegacyAbi`/`updateLegacyAbi`.** The `Legacy`\npair is what Kotlin's own ABI validation documentation calls these tasks, but here they come straight\nfrom KGP and therefore exist *only on the JVM modules* — they silently skip every Android library\nmodule, which is most of the published ones. A green `updateLegacyAbi` means less than half the repo\nwas covered.\n\n**Some dependency versions are deliberately old.** The `compileOnly` AndroidX versions in\n`gradle/libs.versions.toml` are pinned to the *lowest* version LeakCanary supports, so that apps\nresolve to their own newer version without needing a resolution strategy. The inline comments say\nwhich ones and why. Don't bump them to fix a warning.\n\n**`HprofRetainedHeapPerfTest` and `HprofIOPerfTest` freeze exact numbers** — bytes read, and memory\nretained at each analysis step, within a margin. A change to how the analysis allocates or reads will\nfail them. That's the point: they exist to make memory and I/O regressions visible. Investigate\nbefore adjusting the expected values, and say in the PR why the new number is correct.\n\n**detekt runs on pre-push and in CI**, config at `config/detekt-config.yml`. The hook installs itself\nvia the `assemble` and `clean` tasks, so a fresh clone gets it after the first build. Run `detekt`\nbefore pushing rather than discovering it at push time.\n\n**`gh pr merge --auto` does not wait for CI here — it merges on the spot.** Auto-merge is enabled on\nthe repo, but `main` is deliberately left unprotected, so there are no required status checks for\nauto-merge to gate on. GitHub sees a mergeable pull request with nothing to wait for and merges\nimmediately, exiting zero and printing nothing, which reads exactly like it armed. Nothing in the\nrepo will stop a merge while CI is red — `main` is open on purpose — so waiting for green is your\njob, not the platform's. Wait explicitly and let the exit code decide:\n\n```bash\ngh pr checks <number> --watch --fail-fast && gh pr merge <number> --merge\n```\n\n`gh pr checks` exits zero only once every check has passed, so the `&&` is what makes this safe;\n`--fail-fast` returns as soon as one fails instead of sitting through the rest. A run takes 9 to 13\nminutes, nearly all of it the emulator matrix, so start that command detached — a foreground call\nthat gives up at ten minutes will usually be killed just before the last emulator reports.\n\n## Changelog\n\nEntries go in `docs/changelog.md` under `## Unreleased`, each starting with one of the markers from\nthe legend at the top of that file. Pick the marker from what the change *is*, not from how big it\nfeels, and grep for a comparable existing entry rather than guessing.\n\n**💥 means a crash fix here, not a breaking change** — the opposite of the\n[gitmoji](https://gitmoji.dev/) convention, and the mistake that convention trips people into.\nReaching for 💥 because a change feels impactful tells readers a crash was fixed when nothing\ncrashed. Breaking changes are ⚠️; when one needs more than a bullet, write it as a\n`### Breaking change: <summary>` heading with prose.\n\n**The changelog is for changes that matter to the people consuming LeakCanary**, not a record of\nevery diff. Refactors, internal cleanups and test-only changes usually don't need an entry.\n\n**`docs/changelog.md` is the only changelog in this repo, and it covers the libraries.** Shark Dive is\nreleased separately and has **no changelog**, because it has had no release — so a change to\n`shark/shark-dive/` gets no entry anywhere, and **don't create a file for one**. There was one, and it\nwas a hundred unreleased bullets describing the app to nobody; `docs/releasing-shark-dive.md` has why it\nwent and when starting one would be right.\n\n## Conventions\n\n- When a function's parameters don't fit on one line, put **each on its own line** — the existing\n  code is consistent about this and detekt won't tell you.\n- Commit subjects are imperative and describe the change, e.g. \"Keep modules without a public API off\n  the documentation site\". Explain *why* in the body when it isn't obvious.\n- Don't leave test-only or unused code in the committed tree. If scaffolding was needed to get\n  somewhere, remove it before the PR lands.\n- **The prose conventions in this repo are for the repo, not for the screen.** The guides, the KDoc and\n  the `notes/` files are written at length on purpose — the reader is someone about to change the code,\n  and the cost of a paragraph there is nothing. What a *user interface* says is governed by the opposite\n  rule: a label is one or two words, and the paragraph explaining it lives somewhere the reader goes\n  looking for it. `shark/shark-dive/AGENTS.md` has that rule and the mechanism, and Shark Dive is the\n  only substantial UI in this repo, so it is the only place it applies.\n- Test heap dumps are built with the `dump { }` DSL from `shark-hprof-test` (see `docs/dev-env.md`)\n  rather than committed as binary fixtures. Never hand-assemble hprof bytes. For a large realistic\n  dump, drive a real JVM via `HotSpotDiagnosticMXBean.dumpHeap`.\n- Tests use JUnit 4 and AssertJ. Instrumentation tests depend on `libs.assertjCore.android` rather\n  than `libs.assertjCore`, because AssertJ 3.16 and up can't load on API 24 — the catalog comment\n  says why. So an assertion that a unit test can use may not compile in an instrumentation test.\n\n## Scoped guides\n\nSubdirectories may carry their own `AGENTS.md`, and the closest one to the file being edited wins,\nlike `.gitignore`. Prefer putting guidance in the narrowest place it applies over growing this file —\na module's quirks belong next to the module.\n\nClaude Code reads `CLAUDE.md` rather than `AGENTS.md`, so each `AGENTS.md` is paired with a\n`CLAUDE.md` containing `@AGENTS.md`. Add both when adding a scoped guide.\n",".cursor/mcp.json":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/square/leakcanary/HEAD/.cursor/mcp.json)",".claude-plugin/marketplace.json":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/square/leakcanary/HEAD/.claude-plugin/marketplace.json)",".claude-plugin/plugin.json":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/square/leakcanary/HEAD/.claude-plugin/plugin.json)"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/square/leakcanary/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# LeakCanary — agent guide\n\nA memory leak detection library for Android, plus Shark, the heap analyzer underneath it. Published\nto Maven Central and consumed by a very large number of apps, so **the public API and the bytecode\nlevel are contracts**, not implementation details.\n\nNot equally, though. The `leakcanary*` modules are what apps depend on directly, and their public API\nis the contract that matters most: breaking backward compatibility there is a last resort. `shark*` is\nused far less, so a breaking change is on the table there when it buys a meaningful improvement. In\nboth cases the ABI dump is what makes the break deliberate instead of a surprise, so propose it rather\nthan assuming it's fine.\n\nThis file records what an agent would get wrong from reading the source alone. Anything derivable by\nreading the code belongs in the code, not here — please keep it that way when editing.\n\n## Layout\n\n| Directory | What's in it |\n| --- | --- |\n| `shark/` | Heap dump parsing and analysis. Plain JVM, no Android dependency, except `shark-android` which adds Android specific reference readers and matchers. |\n| `object-watcher/` | Watches objects for retention. The lowest layer, usable on its own. |\n| `leakcanary/` | The Android library, plus `leakcanary-app*`, a standalone UI app that is not part of the library. |\n| `plumber/` | Fixes for known Android framework leaks, installed automatically. |\n| `samples/` | Sample app. |\n| `docs/` | The [documentation site](https://square.github.io/leakcanary/) content. |\n\nThe group directories (`shark/`, `leakcanary/`, …) hold no code of their own — only modules do.\n\n## Build and test\n\n`docs/dev-env.md` is the contributor setup guide — code style, local deployment, examples of the\nsynthetic heap dump DSL. Read it for anything this section doesn't cover.\n\nBuilt with **Java 17**, targeting **Java 8 bytecode** repo wide. Both matter: consumers still on\nJava 8 have to be able to use the artifacts.\n\n```bash\n./gradlew build                       # what CI runs\n./gradlew :shark:shark:test           # one module's unit tests\n./gradlew detekt                      # static analysis, also run by the pre-push hook\n./gradlew updateKotlinAbi             # after any public API change, see below\n./gradlew siteDokka                   # regenerate docs/api\n```\n\n`docs/api/` — the `siteDokka` output — is git ignored, not committed. The release process\nregenerates it just before publishing the site (see `docs/releasing.md`), so a public API change\nmeans updating the ABI dump and nothing else. If you do run `siteDokka`, don't edit what it writes;\nfix the KDoc in the source.\n\nInstrumentation tests need a device or emulator and only cover `leakcanary-android`,\n`leakcanary-android-core`, `leakcanary-android-instrumentation` and `leakcanary-android-test`. CI\nruns them on one emulator per major Android release, from the minSdk to the newest API level with a\nsystem image, so a change that only works on some API levels will fail there rather than locally.\n\n## Things that will bite you\n\n**Public API changes fail the build until the ABI dump is updated.** `checkKotlinAbi` compares the\npublic ABI against the committed `api/*.api` files and runs as part of `check`, so `./gradlew build`\ncatches it. When it fails, run `./gradlew updateKotlinAbi` and commit the changed `api/*.api` files —\nbut read the diff first, because an unintended ABI change is exactly what this is meant to catch.\nModules with no public API are exempt; they're listed in `modulesWithoutPublicApi` in the root\n`build.gradle.kts`.\n\n**There are two ABI validation mechanisms, deliberately sharing task names** so that one command\ncovers the whole repo: the Kotlin Gradle plugin's `abiValidation()` for JVM modules, and an\nequivalent pair of tasks hand-rolled in the root `build.gradle.kts` for Android library modules,\nwhich KGP doesn't support yet ([KT-83410](https://youtrack.jetbrains.com/issue/KT-83410)).\n\n**So use `checkKotlinAbi`/`updateKotlinAbi`, never `checkLegacyAbi`/`updateLegacyAbi`.** The `Legacy`\npair is what Kotlin's own ABI validation documentation calls these tasks, but here they come straight\nfrom KGP and therefore exist *only on the JVM modules* — they silently skip every Android library\nmodule, which is most of the published ones. A green `updateLegacyAbi` means less than half the repo\nwas covered.\n\n**Some dependency versions are deliberately old.** The `compileOnly` AndroidX versions in\n`gradle/libs.versions.toml` are pinned to the *lowest* version LeakCanary supports, so that apps\nresolve to their own newer version without needing a resolution strategy. The inline comments say\nwhich ones and why. Don't bump them to fix a warning.\n\n**`HprofRetainedHeapPerfTest` and `HprofIOPerfTest` freeze exact numbers** — bytes read, and memory\nretained at each analysis step, within a margin. A change to how the analysis allocates or reads will\nfail them. That's the point: they exist to make memory and I/O regressions visible. Investigate\nbefore adjusting the expected values, and say in the PR why the new number is correct.\n\n**detekt runs on pre-push and in CI**, config at `config/detekt-config.yml`. The hook installs itself\nvia the `assemble` and `clean` tasks, so a fresh clone gets it after the first build. Run `detekt`\nbefore pushing rather than discovering it at push time.\n\n**`gh pr merge --auto` does not wait for CI here — it merges on the spot.** Auto-merge is enabled on\nthe repo, but `main` is deliberately left unprotected, so there are no required status checks for\nauto-merge to gate on. GitHub sees a mergeable pull request with nothing to wait for and merges\nimmediately, exiting zero and printing nothing, which reads exactly like it armed. Nothing in the\nrepo will stop a merge while CI is red — `main` is open on purpose — so waiting for green is your\njob, not the platform's. Wait explicitly and let the exit code decide:\n\n```bash\ngh pr checks <number> --watch --fail-fast && gh pr merge <number> --merge\n```\n\n`gh pr checks` exits zero only once every check has passed, so the `&&` is what makes this safe;\n`--fail-fast` returns as soon as one fails instead of sitting through the rest. A run takes 9 to 13\nminutes, nearly all of it the emulator matrix, so start that command detached — a foreground call\nthat gives up at ten minutes will usually be killed just before the last emulator reports.\n\n## Changelog\n\nEntries go in `docs/changelog.md` under `## Unreleased`, each starting with one of the markers from\nthe legend at the top of that file. Pick the marker from what the change *is*, not from how big it\nfeels, and grep for a comparable existing entry rather than guessing.\n\n**💥 means a crash fix here, not a breaking change** — the opposite of the\n[gitmoji](https://gitmoji.dev/) convention, and the mistake that convention trips people into.\nReaching for 💥 because a change feels impactful tells readers a crash was fixed when nothing\ncrashed. Breaking changes are ⚠️; when one needs more than a bullet, write it as a\n`### Breaking change: <summary>` heading with prose.\n\n**The changelog is for changes that matter to the people consuming LeakCanary**, not a record of\nevery diff. Refactors, internal cleanups and test-only changes usually don't need an entry.\n\n**`docs/changelog.md` is the only changelog in this repo, and it covers the libraries.** Shark Dive is\nreleased separately and has **no changelog**, because it has had no release — so a change to\n`shark/shark-dive/` gets no entry anywhere, and **don't create a file for one**. There was one, and it\nwas a hundred unreleased bullets describing the app to nobody; `docs/releasing-shark-dive.md` has why it\nwent and when starting one would be right.\n\n## Conventions\n\n- When a function's parameters don't fit on one line, put **each on its own line** — the existing\n  code is consistent about this and detekt won't tell you.\n- Commit subjects are imperative and describe the change, e.g. \"Keep modules without a public API off\n  the documentation site\". Explain *why* in the body when it isn't obvious.\n- Don't leave test-only or unused code in the committed tree. If scaffolding was needed to get\n  somewhere, remove it before the PR lands.\n- **The prose conventions in this repo are for the repo, not for the screen.** The guides, the KDoc and\n  the `notes/` files are written at length on purpose — the reader is someone about to change the code,\n  and the cost of a paragraph there is nothing. What a *user interface* says is governed by the opposite\n  rule: a label is one or two words, and the paragraph explaining it lives somewhere the reader goes\n  looking for it. `shark/shark-dive/AGENTS.md` has that rule and the mechanism, and Shark Dive is the\n  only substantial UI in this repo, so it is the only place it applies.\n- Test heap dumps are built with the `dump { }` DSL from `shark-hprof-test` (see `docs/dev-env.md`)\n  rather than committed as binary fixtures. Never hand-assemble hprof bytes. For a large realistic\n  dump, drive a real JVM via `HotSpotDiagnosticMXBean.dumpHeap`.\n- Tests use JUnit 4 and AssertJ. Instrumentation tests depend on `libs.assertjCore.android` rather\n  than `libs.assertjCore`, because AssertJ 3.16 and up can't load on API 24 — the catalog comment\n  says why. So an assertion that a unit test can use may not compile in an instrumentation test.\n\n## Scoped guides\n\nSubdirectories may carry their own `AGENTS.md`, and the closest one to the file being edited wins,\nlike `.gitignore`. Prefer putting guidance in the narrowest place it applies over growing this file —\na module's quirks belong next to the module.\n\nClaude Code reads `CLAUDE.md` rather than `AGENTS.md`, so each `AGENTS.md` is paired with a\n`CLAUDE.md` containing `@AGENTS.md`. Add both when adding a scoped guide.\n","isInternal":false,"tokens":2366,"sizeBytes":9815},{"name":"marketplace.json","path":".claude-plugin/marketplace.json","rawUrl":"https://raw.githubusercontent.com/square/leakcanary/HEAD/.claude-plugin/marketplace.json","title":"Claude Plugin Marketplace Catalog","category":"marketplace","format":"json","content":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/square/leakcanary/HEAD/.claude-plugin/marketplace.json)","tokens":300,"sizeBytes":196},{"name":"plugin.json","path":".claude-plugin/plugin.json","rawUrl":"https://raw.githubusercontent.com/square/leakcanary/HEAD/.claude-plugin/plugin.json","title":"claude-plugin Plugin Manifest","category":"plugin-manifest","format":"json","content":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/square/leakcanary/HEAD/.claude-plugin/plugin.json)","tokens":300,"sizeBytes":182},{"name":"mcp.json","path":".cursor/mcp.json","rawUrl":"https://raw.githubusercontent.com/square/leakcanary/HEAD/.cursor/mcp.json","title":"Model Context Protocol (MCP) Configuration","category":"mcp-config","format":"json","content":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/square/leakcanary/HEAD/.cursor/mcp.json)","tokens":300,"sizeBytes":175}],"systemPromptSnippet":"<yakaai_skills repo=\"square/leakcanary\">\n<!-- File: AGENTS.md (Tokens: ~2366 | Category: root-instruction) -->\n# LeakCanary — agent guide\n\nA memory leak detection library for Android, plus Shark, the heap analyzer underneath it. Published\nto Maven Central and consumed by a very large number of apps, so **the public API and the bytecode\nlevel are contracts**, not implementation details.\n\nNot equally, though. The `leakcanary*` modules are what apps depend on directly, and their public API\nis the contract that matters most: breaking backward compatibility there is a last resort. `shark*` is\nused far less, so a breaking change is on the table there when it buys a meaningful improvement. In\nboth cases the ABI dump is what makes the break deliberate instead of a surprise, so propose it rather\nthan assuming it's fine.\n\nThis file records what an agent would get wrong from reading the source alone. Anything derivable by\nreading the code belongs in the code, not here — please keep it that way when editing.\n\n## Layout\n\n| Directory | What's in it |\n| --- | --- |\n| `shark/` | Heap dump parsing and analysis. Plain JVM, no Android dependency, except `shark-android` which adds Android specific reference readers and matchers. |\n| `object-watcher/` | Watches objects for retention. The lowest layer, usable on its own. |\n| `leakcanary/` | The Android library, plus `leakcanary-app*`, a standalone UI app that is not part of the library. |\n| `plumber/` | Fixes for known Android framework leaks, installed automatically. |\n| `samples/` | Sample app. |\n| `docs/` | The [documentation site](https://square.github.io/leakcanary/) content. |\n\nThe group directories (`shark/`, `leakcanary/`, …) hold no code of their own — only modules do.\n\n## Build and test\n\n`docs/dev-env.md` is the contributor setup guide — code style, local deployment, examples of the\nsynthetic heap dump DSL. Read it for anything this section doesn't cover.\n\nBuilt with **Java 17**, targeting **Java 8 bytecode** repo wide. Both matter: consumers still on\nJava 8 have to be able to use the artifacts.\n\n```bash\n./gradlew build                       # what CI runs\n./gradlew :shark:shark:test           # one module's unit tests\n./gradlew detekt                      # static analysis, also run by the pre-push hook\n./gradlew updateKotlinAbi             # after any public API change, see below\n./gradlew siteDokka                   # regenerate docs/api\n```\n\n`docs/api/` — the `siteDokka` output — is git ignored, not committed. The release process\nregenerates it just before publishing the site (see `docs/releasing.md`), so a public API change\nmeans updating the ABI dump and nothing else. If you do run `siteDokka`, don't edit what it writes;\nfix the KDoc in the source.\n\nInstrumentation tests need a device or emulator and only cover `leakcanary-android`,\n`leakcanary-android-core`, `leakcanary-android-instrumentation` and `leakcanary-android-test`. CI\nruns them on one emulator per major Android release, from the minSdk to the newest API level with a\nsystem image, so a change that only works on some API levels will fail there rather than locally.\n\n## Things that will bite you\n\n**Public API changes fail the build until the ABI dump is updated.** `checkKotlinAbi` compares the\npublic ABI against the committed `api/*.api` files and runs as part of `check`, so `./gradlew build`\ncatches it. When it fails, run `./gradlew updateKotlinAbi` and commit the changed `api/*.api` files —\nbut read the diff first, because an unintended ABI change is exactly what this is meant to catch.\nModules with no public API are exempt; they're listed in `modulesWithoutPublicApi` in the root\n`build.gradle.kts`.\n\n**There are two ABI validation mechanisms, deliberately sharing task names** so that one command\ncovers the whole repo: the Kotlin Gradle plugin's `abiValidation()` for JVM modules, and an\nequivalent pair of tasks hand-rolled in the root `build.gradle.kts` for Android library modules,\nwhich KGP doesn't support yet ([KT-83410](https://youtrack.jetbrains.com/issue/KT-83410)).\n\n**So use `checkKotlinAbi`/`updateKotlinAbi`, never `checkLegacyAbi`/`updateLegacyAbi`.** The `Legacy`\npair is what Kotlin's own ABI validation documentation calls these tasks, but here they come straight\nfrom KGP and therefore exist *only on the JVM modules* — they silently skip every Android library\nmodule, which is most of the published ones. A green `updateLegacyAbi` means less than half the repo\nwas covered.\n\n**Some dependency versions are deliberately old.** The `compileOnly` AndroidX versions in\n`gradle/libs.versions.toml` are pinned to the *lowest* version LeakCanary supports, so that apps\nresolve to their own newer version without needing a resolution strategy. The inline comments say\nwhich ones and why. Don't bump them to fix a warning.\n\n**`HprofRetainedHeapPerfTest` and `HprofIOPerfTest` freeze exact numbers** — bytes read, and memory\nretained at each analysis step, within a margin. A change to how the analysis allocates or reads will\nfail them. That's the point: they exist to make memory and I/O regressions visible. Investigate\nbefore adjusting the expected values, and say in the PR why the new number is correct.\n\n**detekt runs on pre-push and in CI**, config at `config/detekt-config.yml`. The hook installs itself\nvia the `assemble` and `clean` tasks, so a fresh clone gets it after the first build. Run `detekt`\nbefore pushing rather than discovering it at push time.\n\n**`gh pr merge --auto` does not wait for CI here — it merges on the spot.** Auto-merge is enabled on\nthe repo, but `main` is deliberately left unprotected, so there are no required status checks for\nauto-merge to gate on. GitHub sees a mergeable pull request with nothing to wait for and merges\nimmediately, exiting zero and printing nothing, which reads exactly like it armed. Nothing in the\nrepo will stop a merge while CI is red — `main` is open on purpose — so waiting for green is your\njob, not the platform's. Wait explicitly and let the exit code decide:\n\n```bash\ngh pr checks <number> --watch --fail-fast && gh pr merge <number> --merge\n```\n\n`gh pr checks` exits zero only once every check has passed, so the `&&` is what makes this safe;\n`--fail-fast` returns as soon as one fails instead of sitting through the rest. A run takes 9 to 13\nminutes, nearly all of it the emulator matrix, so start that command detached — a foreground call\nthat gives up at ten minutes will usually be killed just before the last emulator reports.\n\n## Changelog\n\nEntries go in `docs/changelog.md` under `## Unreleased`, each starting with one of the markers from\nthe legend at the top of that file. Pick the marker from what the change *is*, not from how big it\nfeels, and grep for a comparable existing entry rather than guessing.\n\n**💥 means a crash fix here, not a breaking change** — the opposite of the\n[gitmoji](https://gitmoji.dev/) convention, and the mistake that convention trips people into.\nReaching for 💥 because a change feels impactful tells readers a crash was fixed when nothing\ncrashed. Breaking changes are ⚠️; when one needs more than a bullet, write it as a\n`### Breaking change: <summary>` heading with prose.\n\n**The changelog is for changes that matter to the people consuming LeakCanary**, not a record of\nevery diff. Refactors, internal cleanups and test-only changes usually don't need an entry.\n\n**`docs/changelog.md` is the only changelog in this repo, and it covers the libraries.** Shark Dive is\nreleased separately and has **no changelog**, because it has had no release — so a change to\n`shark/shark-dive/` gets no entry anywhere, and **don't create a file for one**. There was one, and it\nwas a hundred unreleased bullets describing the app to nobody; `docs/releasing-shark-dive.md` has why it\nwent and when starting one would be right.\n\n## Conventions\n\n- When a function's parameters don't fit on one line, put **each on its own line** — the existing\n  code is consistent about this and detekt won't tell you.\n- Commit subjects are imperative and describe the change, e.g. \"Keep modules without a public API off\n  the documentation site\". Explain *why* in the body when it isn't obvious.\n- Don't leave test-only or unused code in the committed tree. If scaffolding was needed to get\n  somewhere, remove it before the PR lands.\n- **The prose conventions in this repo are for the repo, not for the screen.** The guides, the KDoc and\n  the `notes/` files are written at length on purpose — the reader is someone about to change the code,\n  and the cost of a paragraph there is nothing. What a *user interface* says is governed by the opposite\n  rule: a label is one or two words, and the paragraph explaining it lives somewhere the reader goes\n  looking for it. `shark/shark-dive/AGENTS.md` has that rule and the mechanism, and Shark Dive is the\n  only substantial UI in this repo, so it is the only place it applies.\n- Test heap dumps are built with the `dump { }` DSL from `shark-hprof-test` (see `docs/dev-env.md`)\n  rather than committed as binary fixtures. Never hand-assemble hprof bytes. For a large realistic\n  dump, drive a real JVM via `HotSpotDiagnosticMXBean.dumpHeap`.\n- Tests use JUnit 4 and AssertJ. Instrumentation tests depend on `libs.assertjCore.android` rather\n  than `libs.assertjCore`, because AssertJ 3.16 and up can't load on API 24 — the catalog comment\n  says why. So an assertion that a unit test can use may not compile in an instrumentation test.\n\n## Scoped guides\n\nSubdirectories may carry their own `AGENTS.md`, and the closest one to the file being edited wins,\nlike `.gitignore`. Prefer putting guidance in the narrowest place it applies over growing this file —\na module's quirks belong next to the module.\n\nClaude Code reads `CLAUDE.md` rather than `AGENTS.md`, so each `AGENTS.md` is paired with a\n`CLAUDE.md` containing `@AGENTS.md`. Add both when adding a scoped guide.\n\n<!-- File: .claude-plugin/marketplace.json (Tokens: ~300 | Category: marketplace) -->\n# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/square/leakcanary/HEAD/.claude-plugin/marketplace.json)\n\n<!-- File: .claude-plugin/plugin.json (Tokens: ~300 | Category: plugin-manifest) -->\n# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/square/leakcanary/HEAD/.claude-plugin/plugin.json)\n\n<!-- File: .cursor/mcp.json (Tokens: ~300 | Category: mcp-config) -->\n# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/square/leakcanary/HEAD/.cursor/mcp.json)\n</yakaai_skills>"}