Pre-production readiness checker for Android — catches the classic "10 production mistakes" before code ships, and auto-fixes them with your permission.
| Layer | Engine | Covers | Needs API key? |
|---|---|---|---|
| 1 — Deterministic | Custom Detekt rules via detekt-cli | !! usage · main-thread blocking (UI files only) · sensitive logging · hardcoded secrets |
No — offline |
| 2 — Agentic | Claude (tool-use loop over your git diff) | duplicate API calls · logic in Activities/Composables · missing error/empty states · token refresh · config change/process death · crash monitoring gaps | Yes |
| Auto-fix | Validated propose_edit agent pipeline |
Fixes findings from both layers; edits are exact-match validated; patches generated by the tool, never by the model | Yes |
# 1. Unpack to its permanent home
unzip prodcheck-v3.zip && mv prodcheck ~/prodcheck && cd ~/prodcheck
# 2. Gradle wrapper (zip ships without one — borrow from any Android project)
cp ~/AndroidStudioProjects/<AnyProject>/gradlew .
cp -R ~/AndroidStudioProjects/<AnyProject>/gradle .
chmod +x gradlew
# 3. Build the rules jar + agent binary
./gradlew :detekt-rules:jar :agent:installDist
# 4. Standalone detekt-cli
mkdir -p tools && cd tools
curl -L -o detekt-cli.zip https://github.com/detekt/detekt/releases/download/v1.23.7/detekt-cli-1.23.7.zip
unzip -q detekt-cli.zip && mv detekt-cli-1.23.7 detekt-cli
cd ..
# 5. PATH + API key (Layer 2 / auto-fix only — Layer 1 works without it)
echo 'export PATH="$HOME/prodcheck/bin:$PATH"' >> ~/.zshrc
echo 'export ANTHROPIC_API_KEY=sk-ant-...' >> ~/.zshrc # your REAL key
source ~/.zshrc
chmod +x ~/prodcheck/bin/prodcheck
# 6. Self-test — expect 4 findings from BadExampleActivity.kt
prodcheck --l1 ~/prodcheck
cat ~/prodcheck/build/reports/prodcheck-layer1.mdAPI credits: console.anthropic.com → Plans & Billing. $5 covers hundreds of diff-scoped runs; a typical fix run costs cents.
prodcheck --l1 <project> # Layer 1 scan only. Offline, free, whole project.
prodcheck --l1fix <project> # L1 scan + auto-fix. Asks [y/N] before touching files.
prodcheck <project> # L1 scan + L2 diff review -> writes a fix PATCH (no auto-apply).
prodcheck --all <project> # Everything: L1 -> ask -> fix -> re-scan ->
# L2 review -> ask -> fix -> compile check.
prodcheck --all --yes <proj> # Same, but skips prompts (CI / scripted use).<project> defaults to the current directory (prodcheck --all .).
Always use the exact folder name (HotfixAgent, not hotfixagent) — macOS
is case-insensitive but patch paths are not.
# 1. Finish a feature/bugfix. DO NOT commit yet —
# Layer 2 reviews your uncommitted diff (git diff HEAD).
cd ~/AndroidStudioProjects/MyApp
prodcheck --all .
# 2. Read the findings shown before each [y/N] prompt.
# y -> fixes are applied to your working tree
# n -> skipped (L1 mode falls back to writing a patch file)
# 3. ALWAYS review what the agent changed before committing:
git diff # or: git diff | cat (no pager)
# 4a. Happy -> commit code + fixes together:
git add -A
git commit -m "Add <feature> (ProdCheck verified)"
# 4b. Unhappy with a specific fix -> undo just that file:
git checkout -- path/to/File.kt
# 4c. Undo ALL applied fixes:
git checkout -- .Golden rules
- Run from a state where everything except your current work is committed —
that makes
git diffreview clean and undo safe. - Never commit an auto-fix you haven't read. The agent's edits are validated mechanically (exact-match, compile-checked) but semantic correctness is yours to confirm — we have watched it produce a plausible, confidently justified, wrong fix (see Lessons below).
- A clean tree means Layer 2 reviews the last commit instead — useful for post-hoc review, but the intended flow is pre-commit.
Real !! on purpose (crash tests, fixtures)? Suppress instead of fixing:
@Suppress("DoubleBang") // intentional: NPE crash-test entry
value!!.lengthDetekt honours @Suppress natively; the finding disappears from every future scan.
All inside the target project (wiped by ./gradlew clean — copy out anything you
want to keep):
| File | What |
|---|---|
build/reports/prodcheck-layer1.md |
L1 findings with code snippets + line pointers |
build/reports/prodcheck-layer2.md |
Agent verdict (PASS / WARN / BLOCK) + findings + fixes |
build/reports/prodcheck-fix.patch |
Fix patch when you declined direct apply — git apply it |
build/reports/prodcheck-layer2-raw.txt |
Raw agent output, only written if JSON parsing failed |
Exit code is 1 on a BLOCK verdict — wire prodcheck --all --yes . straight into CI.
Settings → Tools → External Tools → +
| Field | Value |
|---|---|
| Name | ProdCheck L1 |
| Program | /Users/<you>/prodcheck/bin/prodcheck |
| Arguments | --l1 $ProjectFileDir$ |
| Working directory | $ProjectFileDir$ |
Then: Tools → External Tools → ProdCheck L1 in any project. Add a second entry
with arguments --all --yes $ProjectFileDir$ for the full pipeline (External
Tools consoles can't answer prompts, hence --yes — review with git diff after).
For interactive --all, use the Studio Terminal tab instead.
Edit detekt-rules/src/main/kotlin/.../ProdCheckRules.kt, then:
cd ~/prodcheck && ./gradlew :detekt-rules:jarAgent prompt / fixer guidance lives in agent/src/main/kotlin/.../Main.kt
(review rules R2–R10) and Layer1Fix.kt (per-rule fix guidance), then:
cd ~/prodcheck && ./gradlew :agent:installDistEvery project picks the changes up on its next run — zero per-project config.
MainThreadBlockingfires only in UI-adjacent files (*Activity.kt,*Fragment.kt,*Screen.kt,@Composable) — repository/store code usually already runs onDispatchers.IO, which Layer 1 can't verify. That judgement belongs to Layer 2.getwas removed from the blocking-call list (JSONArray.getetc. = pure noise).- Layer 1 policy: precision over recall. A rule developers can't trust is a rule they'll ignore.
- The agent once replaced an intentional
!!(NPE crash-test) withrequireNotNulland confidently claimed the behaviour was identical. It is not —requireNotNullthrowsIllegalArgumentException, notNullPointerException. On retry it defended the same error with a more elaborate wrong justification. Fluency ≠ correctness. - Mitigation: intent-first guidance in the fixer prompt (deliberate
!!→ propose@Suppress, never behaviour changes). Measured result: the next real fix (LKRRate smart-cast refactor) was textbook-correct. - Every harness bug we hit (loop control, path case-sensitivity, non-JSON final answers) was in the deterministic plumbing, not the LLM. Agentic reliability = harness engineering.
| Symptom | Fix |
|---|---|
command not found: prodcheck |
source ~/.zshrc && hash -r; check ls ~/prodcheck/bin |
permission denied: prodcheck |
chmod +x ~/prodcheck/bin/prodcheck |
no such file: ./gradlew |
Copy wrapper from any Android project (setup step 2) |
no repositories are defined |
Your settings.gradle.kts is outdated — needs the dependencyResolutionManagement block from v3 |
credit balance is too low |
console.anthropic.com → Plans & Billing → add credits |
ANTHROPIC_API_KEY not set |
Real key in ~/.zshrc (not the sk-ant-YOUR-KEY placeholder), then source ~/.zshrc |
Patch has a/../... paths |
You ran with a wrong-case path — use the exact folder name |
No diff found — nothing to review |
Tree is clean; L2 needs uncommitted changes (or reviews last commit) |
| Junk findings from unexpected folders | A stray copy of prodcheck (or other vendored code) inside the project — delete it |
Output stuck at : |
That's the git pager — press q (or use git --no-pager diff) |
- Self-verification loop — agent applies patch → compiles → re-scans → rejects/retries on failure, automatically (today compile check only warns)
- Baseline file — suppress legacy findings, gate only new code
- GitHub Actions PR bot — findings as PR review comments, zero-install for teams
- Compose-specific L1 rules (
remembervsrememberSaveable, unkeyed calls) - Fix-acceptance tracking → precision metrics over time