Back to Articles

How to make Android Codebases AI Audit Friendly

Android teams are using LLMs to do codebase health checks — reviewing dependency hygiene, catching security misconfigurations, flagging deprecated APIs, checking Play policy compliance. The results are disappointing. The findings feel shallow, vary run to run, and miss issues a senior engineer would catch in minutes. The model is not the problem. The project is not LLM-friendly — and making it LLM-friendly is the new engineering discipline that most teams have not yet tackled.

The Real Problem: Android Projects Are Not LLM-Friendly

Android codebases need continuous attention. The platform does not stand still — targetSdk requirements ratchet upward, deprecated APIs break across AGP upgrades, third-party dependencies accumulate CVEs, Play Store policies tighten. A codebase that shipped cleanly six months ago may already carry meaningful risk today without a single line of new code being written. Keeping on top of this manually means burning senior engineering time on recurring reviews that produce inconsistent output and cannot be run on every pull request. It does not scale.

So teams reach for LLMs. The first experiment is almost always the same: copy the Gradle file, the manifest, and a few Kotlin source files into the chat window, then ask the model what needs attention. It is attractive because the barrier is zero. It rarely produces useful results.

You may have tried this. You may have gotten findings that sounded plausible but felt shallow. You may have seen the model miss issues that a dependency resolver would catch in one pass. You may have noticed it give different findings for the same project depending on which files you included or how you framed the question.

Those outcomes are not a sign that the model is inadequate. They are a sign that the model was being asked to do two jobs at once: extract facts from unstructured files and then reason over those facts. Both steps introduce noise, and the noise compounds.

Raw-file prompting is not a prompt engineering problem. It is the symptom of a project that has not been made LLM-friendly.

What "Agent-Readable" Actually Means

When developers hear "observability," they think of monitoring dashboards, distributed traces, and OpenTelemetry pipelines. That is not the kind of observability this article is about.

An agent-readable Android project surfaces dependency state, security findings, rendered UI artifacts, and test outcomes as structured data that any automated system — or a developer running the right tools — can inspect without guessing. Most Android projects do not do this today.

The gap is straightforward: the raw project files tell you what was declared and what was written. They do not tell you what actually resolved, what a screen looks like when rendered, whether a component is exposed to external apps, or which tests are flaking in CI. Those facts exist — but only after specific tools run and produce structured output. Until they do, any reviewer working from raw files alone, human or AI, is working from incomplete information.

A senior Android engineer reviewing an unfamiliar project does not read files sequentially. They pull up the resolved dependency graph. They look at the manifest through a security lens. They open Compose previews to see what the UI actually renders to. They check CI failure patterns. They build a picture from structured evidence before forming a judgment. An agent given only the raw source files cannot reach the same evidence — and the fix is the same for both: run the right tools, and make their output available.

Making a project agent-readable is an architecture decision, not a prompting decision. The project has to be set up to produce the right artifacts.

Where Raw-File Prompting Goes Wrong

A good diagnostician does not diagnose from a description of where the files are kept. A doctor wants structured outputs — blood test results, scan images, vital readings — because raw observation is not reliable or repeatable enough for clinical decisions. Raw-file prompting puts the LLM in both roles simultaneously: instrument and analyst. The failures are predictable.

Dependency state. A BOM declaration in Gradle hides the actual resolved version of each library. A model reading the BOM sees constraint rules. A dependency resolver sees what actually built. A transitive dependency can carry the vulnerable package even when the directly declared dependency looks clean — the model misses it; a resolved dependency graph surfaces it immediately.

Visual state. A Compose layout bug looks correct in source code. It only becomes visible when rendered with long text in dark mode against a small screen. A model reading Composable functions imagines the output. A rendered PNG shows the actual breakage.

Security state. An AndroidManifest.xml can look harmless on a first read. An exported Activity without intent-filter restrictions, a permission declared but never used, cleartext traffic enabled for a single domain — these are findings a parser surfaces in one pass, consistently, every run. A model reading the same XML will catch different ones on different runs.

Behavioral state. A test suite appears healthy in the source files. CI failure signatures — which tests flake, on which devices, under which conditions — are invisible without the behavioral record.

In every case the model is not failing because it lacks capability. It is failing because the project did not give it what it needed. The goal is not to make the model better at guessing from source files. The goal is to remove guessing from the audit path entirely.

An audit that produces different findings every time it runs is not an audit. It is a lottery.

The Four Types of Structured Evidence

Closing the gap means producing four categories of structured evidence before any agent prompt is written.

Structural Facts

Resolved dependency graph, Gradle plugin configuration, and version catalog state. Not what you declared — what actually built. These answer which libraries are in the app, which versions resolved after conflict negotiation, which carry known CVEs, and which arrived transitively without being directly chosen.

Structural evidence
 ├── Resolved dependency graph  → actual versions, CVE candidates, duplicates
 ├── Version catalog state      → pinned vs floating, stale versions
 └── Plugin graph               → AGP version, annotation processors, build tools

If your audit reasons from declarations rather than resolutions, it is not auditing your app — it is auditing your intentions.

Security Signals

Parsed findings from the AndroidManifest: exported components without intent-filter restrictions, permissions declared versus permissions used in code, network security configuration, cleartext traffic domains, backup rules, certificate pinning coverage. A parser either finds them or it does not. The output is stable across runs — which means findings can be compared between audit cycles, not just within a single session.

Manifest security findings (deterministic parser output)
 ├── Exported components        → activities, receivers, providers without guards
 ├── Permission delta           → declared but unused / used but undeclared
 ├── Network security           → cleartext domains, certificate pinning gaps
 └── Backup configuration       → sensitive paths included in auto-backup

Visual Evidence

The category most teams miss entirely — and arguably the most important for Compose-heavy projects. A Composable function describes how a screen should look. The rendered output is a different artifact — and it is the one that reveals clipping, overflow, dark-mode breakage, and accessibility contrast failures that are invisible in DSL.

Rendering @Preview functions to PNG outside Android Studio gives agents something concrete to reason over. The agent sees what the user would see, not what the source code implies they would see.

Compose visual artifacts
 ├── Light and dark variants    → side-by-side for contrast and color findings
 ├── Size permutations          → compact, medium, expanded
 └── State permutations         → empty, loading, error, populated

Test: Does your project currently produce PNG artifacts from Compose previews in CI? If no, any agent reviewing your UI is working from inference about an output it has never seen.

Behavioral Evidence

Test results, Lint output, Detekt findings, and CI failure signatures. The behavioral record of the project — what passed, what failed, how often, and under which conditions. An agent with access to a failing test name, the failure message, and the last-passing commit SHA has a fundamentally different starting point than one asked to "find bugs" from source files alone.

Behavioral record
 ├── Unit test results           → pass/fail per variant, failure messages
 ├── Lint/Detekt structured output → findings with severity and location
 ├── CI failure signatures       → which checks fail, on which branches, how often
 └── Build scan output           → compilation warnings, incremental build health

Source code says what should happen. The behavioral record says what actually happened. An agent without that record is reasoning from intention, not outcome.

The Architecture: Scanners First, Model Second

The separation that makes this work is simple: deterministic code collects the facts, the model synthesizes priorities from those facts. The model never scans raw files.

┌─────────────────────────────────────────────────────────────────┐
│                    EVIDENCE COLLECTION LAYER                    │
│                  (deterministic, repeatable)                    │
│                                                                 │
│  Dependency resolver ──┐                                        │
│  Manifest parser     ──┤──▶  Structured evidence package        │
│  Compose renderer    ──┤         (JSON / stable format)         │
│  Test runner         ──┤                                        │
│  Lint/Detekt         ──┘                                        │
└──────────────────────────────┬──────────────────────────────────┘
                               │
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│                    LLM SYNTHESIS LAYER                          │
│                  (variable, judgment-based)                     │
│                                                                 │
│  Input:  structured evidence package                            │
│  Task:   prioritize findings, explain tradeoffs, recommend      │
│  Output: ranked action list with rationale                      │
│                                                                 │
│  The model never reads raw source files.                        │
│  It reasons only over what the evidence layer produced.         │
└─────────────────────────────────────────────────────────────────┘

This separation is not optional. Without it, findings vary by prompt, by model version, and by which files happened to make it into the context window. With it, the same evidence package produces comparable findings across every audit cycle.

This pattern does not require a specific orchestration framework. DroidDoctor, featured in Android Weekly #725, implements it as a LangGraph state machine with deterministic Python scanners feeding a prioritization step. But the essential requirement is far simpler: fact collection must be deterministic and machine-readable before the model enters the loop. A set of shell scripts feeding structured JSON to an LLM API call achieves the same architecture as a seven-node agent graph, if the separation is maintained.

The quality of an LLM-based Android audit is bounded by the quality of the evidence pipeline, not the capability of the model. A stronger model reasoning over weak evidence produces stronger-sounding guesses — not better findings.

Making It Repeatable: CI Integration

An evidence pipeline that only runs when someone remembers to trigger it locally is not a pipeline. It is a script. The value comes from running it on every pull request and producing comparable structured output over time.

CI pipeline — evidence production and agent audit

  PR opened
       │
       ▼
  Dependency resolution   → structured dependency report
  Manifest parse          → security findings JSON
  Compose preview render  → PNG artifacts per variant/state
  Test run                → results + failure signatures
  Lint/Detekt             → structured findings
       │
       ▼
  Evidence package stored as CI artifact
       │
       ▼
  Agent audit runs against evidence package
       │
       ▼
  Findings posted as PR comment with severity ranking

The agent comment on a PR is generated from a structured evidence package that CI produced for that specific change — not from files the agent read itself. That is the difference between a repeatable audit loop and a prompt experiment.

Test: Does your CI pipeline currently produce any of these four evidence types as structured artifacts? Each one it already produces moves your project closer to agent-readable without requiring a new tooling stack.

The Governance Boundary

The evidence pipeline is also a prerequisite for responsible use of agents with any write access.

Granting an agent write access before the project is agent-readable means it is flying blind. It will produce changes based on inferred dependency state, modify manifest configuration without parsed security findings, and suggest Compose changes without having seen what the current layout renders to. These changes look plausible. They pass a surface-level review. Some of them introduce regressions that take weeks to trace back.

Phase 1 — Agent-readable project
  → Evidence pipeline produces reliable structured output in CI
  → Compose previews render per variant and state
  → Behavioral record is consistent and comparable

Phase 2 — Read-only agent audit
  → Agent reasons over evidence package
  → Findings reviewed and calibrated by engineers
  → False positive rate understood before trusting the output

Phase 3 — Scoped write actions
  → Agent permitted to act on specific, well-bounded finding types
  → Each action produces a diff for human review
  → Human approval required before merge

Write access is not the goal. Reliable, scalable audit coverage is the goal. Write access, if it comes at all, comes after the evidence pipeline is validated and the agent's judgment has been calibrated against real findings.

Test: Before granting any agent write access to your Android project, can you answer yes to all three of these? Does CI produce structured dependency, security, and behavioral artifacts? Does your agent audit run from those artifacts rather than raw files? Is there a human review step between agent output and merged code?

Start Here

Making a project agent-readable does not require a new framework or a new team. These five steps can each be done independently, and each one improves auditability for human reviewers regardless of whether a model is ever involved.

Step 1: Export resolved dependency graphs in CI
        → Save structured output as a CI artifact
        → Focus on resolved versions, not declared versions

Step 2: Parse the manifest into structured security findings
        → Produce JSON findings covering exported components,
          permissions, network config, and backup rules

Step 3: Render Compose previews into image artifacts
        → Run @Preview rendering outside Android Studio in CI
        → Produce light/dark/state permutations as PNG artifacts

Step 4: Persist test and lint results as structured output
        → JUnit XML for test results is sufficient
        → Save Detekt/Lint output as structured findings, not pass/fail

Step 5: Feed those artifacts — not raw files — into an audit agent
        → Give the model the evidence package
        → Ask it to prioritize what a senior Android engineer should act on
        → Treat its output as a ranked starting point, not a final verdict

The first four steps make the project more observable to any reviewer — human or automated. The fifth step is where the LLM enters, and by that point it has what it needs to be useful.

Android codebases will keep drifting. The teams that manage this well are not the ones with the most sophisticated prompts. They are the ones whose projects surface the right evidence — so every reviewer, human or agent, can see what is actually there.