Rust CLI · MIT · pre-alpha

A sharper view of your codebase, tuned for the agent that's about to edit it.

agent-lens is a single-binary code analysis CLI for coding agents. It answers the questions Claude Code and Codex cannot answer from the file they have open — what already duplicates this?, how tangled is this module?, what breaks if I change it? — and emits JSON or compact Markdown built for a context window instead of a terminal.

curl -fsSL https://raw.githubusercontent.com/illumination-k/agent-lens/main/install.sh | bash
Analyzers
23
Languages
4
Hook handlers
5
Telemetry
0

The problem

Agents decide on partial context

An agent reads the file it is editing. What it does not read is the rest of the repository — so it forks a function that already exists, grows the module that is already a bottleneck, and refactors the one function nobody should touch without a plan.

  • The near-duplicate function three modules over that it is about to fork.
  • The impl block whose methods touch disjoint sets of fields and should be split.
  • The module that is a Fan-In bottleneck and should not grow any more.
  • The function whose Cognitive Complexity is 40 and is a landmine to refactor.

The stance is enforced in code, not by convention: println!, eprintln!, unwrap(), and expect() are all clippy deny. Stdout carries protocol payloads and reports; everything else goes to stderr through tracing, so a stray dbg! cannot corrupt a hook response.

What is inside

Three surfaces, one binary

No service, no account, no telemetry — a static binary and a config file.

Hooks

Handlers that speak the Claude Code and Codex hook protocols on stdin/stdout. The agent gets a hotspot thumbnail at session start, a complexity and cohesion heads-up on the file it is about to edit, and a duplicate and wrapper report on the file it just changed. Advisory, never a gate: a failing hook still answers in the agent's own response schema and exits 0.

agent-lens hook setup

Analyzers

Twenty-three on-demand analyses of code shape — code search, duplication, complexity, coupling, call-graph structure, change risk. JSON on stdout by default, compact Markdown with --format md, both sized to drop straight into a prompt.

agent-lens analyze similarity src --format md

Profiles and baselines

Name a repeatable multi-analyzer pass in agent-lens.toml and run it in one command. A baseline reduces that run to a handful of named numbers, so a repository can adopt a threshold without first paying off the debt it already has — only regressions fail.

agent-lens run web

The catalogue

Twenty-three analyzers

Each one is a subcommand: agent-lens analyze <tool> <path>. JSON on stdout by default, --format md for the compact ranking, --top and --min-score to trim it, --diff-only to score just the functions your working tree touched.

Retrieval

Where the codebase does X, when the identifier is not known.

search
Functions ranked by BM25F relevance to a query, tokenized the way identifiers are written — _, camelCase, and the joined form all index as one term. --rank graph re-orders the top hits by call-graph importance so the load-bearing match leads.

Duplication and indirection

What already exists, and how many hops sit between the caller and the work.

similarity
Near-duplicate pairs by TSED tree-edit distance over normalised ASTs, folded into clusters. --target picks functions, type definitions, or statement blocks inside function bodies.
forwarding
Functions that only pass the call along: single hops whose body is a forwarding call modulo ?, .unwrap(), .into(), .await, and the chains they stack into — api::save -> service::save -> repo::save -> db::insert — with the terminus that does the work as the headline and a per-module lasagna roll-up.
narrowable
Declarations wider than their uses: functions one caller needs, traits and interfaces one type implements, parameters that only ever get one value, and pub items whose callers all sit in a narrower scope — each a section with its own caveats.
test-redundancy
Tests that are near-copies of each other, and which one of each set to keep — duplicate or parameterize, with the call graph vetoing a fold that would drop a test's only path to production code.

Shape of a module

How much a unit is holding, and how much has to be read to reason about it.

complexity
Per-function Cyclomatic, Cognitive, Max Nesting Depth, Halstead Volume, and Maintainability Index.
cohesion
LCOM4 per impl block, class, or module unit: the number of connected components in the field-sharing graph.
coupling
Module-level Fan-In, Fan-Out, Henry-Kafura IFC, Martin's Instability, per-pair shared symbols, and the cycles.
communities
The clusters the dependencies form against the module boundaries declared, with the files filed in one module but wired into another.
context-span
The transitive dependency closure per module — how many files an agent must read before it can reason about one.

Call graph

Who calls what, what a change reaches, and which layers were crossed to get there.

function-graph
Nodes and heuristic caller-to-callee edges as visualization-ready JSON, weighted by calls, fan-in/out, LOC, complexity, and MI.
cycles
Function-level strongly connected components with advisory cheapest-cut break suggestions and call-line evidence.
hubs
Outlier fan-out god functions, outlier fan-in blast-radius carriers, Henry-Kafura bottlenecks, and cross-module pull.
layers
Inferred Lakos levelization, entry points, module cycles, and skip-level calls with call-site evidence.
impact
Blast radius of the working-tree diff or a named function: transitive callers folded per depth, plus the reachable tests as a verification checklist.
footprint
The shape of the pending diff: how many functions it touched, which edits sit outside its impact closure, and what it left behind — new wrappers, complexity increases, uncalled additions.
graph-query
One canned traversal per run — callers, callees, neighborhood, or the shortest path between two symbols.

Reachability

Code nothing runs, only tests run, or no test guards.

reach
Who reaches each production function: untested (entry points do, no test does), test-only (a test does, no entry point does), and unreachable in confidence tiers — confirmed rows are deletable on that evidence alone.

Change risk

Where git history and code shape agree that an edit is expensive.

hotspot
Files ranked by commits x cognitive_max over a --since window: where churn and complexity overlap.
risk
The rank product of churn and call-graph centrality, so hot and load-bearing outranks hot but leaf.
co-change
File pairs git history says change together, with the confidence in each direction and the lift that separates a pattern from two merely busy files (any file type).
change-entropy
How scattered each period's change activity was — Shannon entropy of the changed lines per file, summed into a per-file history complexity. --diff-only scores the pending change's scatter against the repository's own commits (any file type).
hidden-coupling
The differential: pairs that co-change with no declared dependency between them, and declared dependencies the window never exercised, kept as two separate buckets.
ownership
Files ranked by low code ownership x churn: top-author share and minor contributors, with identities run through .mailmap, bots filtered and counted, and Co-authored-by trailers credited (any file type).

The output

Output an agent can act on

Dense, ranked, and addressable by file:line — the Markdown form is meant to be pasted into a prompt whole.

$ agent-lens analyze similarity crates/lens-rust/src --format md --top 2 --exclude-tests

# Similarity report: crates/lens-rust/src (tsed method, 219 function(s), threshold 0.85, min lines 5)

## Top 2 similar cluster(s) of 2 total

- 2 functions, similarity 89–89%, identifier overlap 33–33%
  - cohesion.rs:`extract_cohesion_units` (L62-67)
  - type_defs.rs:`extract_type_defs` (L16-21)

- 3 functions, similarity 89–89%, identifier overlap 14–17%
  - call_index.rs:`bound_is_fn_trait` (L509-516)
  - cohesion.rs:`is_self_expr` (L218-226)
  - complexity.rs:`is_rust_keyword` (L194-235)

A real run over this repository's own sources: agent-lens is pointed at itself on every change, and findings about its own code count as findings. Every report is also available as JSON — which is what the function graph viewer on this site renders.

Coverage

Languages

A language-neutral core plus per-language adapters: the metrics are shared, so a new language is one adapter crate rather than a reimplementation.

LanguageParserAnalyzer coverage
RustsynEvery analyzer
TypeScript / JavaScriptoxcAll but the export-status sections
Pythonruff_python_parserAll but the export-status sections
Gotree-sitterEvery analyzer

The test-only and unreachable sections of reach, and the single-impl and visibility sections of narrowable, need extracted export status, which TypeScript and Python do not carry — so they judge Rust and Go only.

Get started

Install

Pick one. The install script verifies the release SHA-256 and fails closed if it cannot.

Install script

Linux x86_64 / arm64 (glibc or musl) and macOS arm64 / x86_64. Pulls the matching tarball from the latest release, verifies its SHA-256, and drops the binary into ~/.local/bin.

curl -fsSL https://raw.githubusercontent.com/illumination-k/agent-lens/main/install.sh | bash

mise

Straight from GitHub Releases, no Rust toolchain, pinned per project.

mise use -g github:illumination-k/agent-lens

Nix flake

Built from source and pinned by flake.lock — no release artifact involved.

nix run github:illumination-k/agent-lens -- --version

From source

The workspace is on edition 2024, so rustc 1.85 or newer.

cargo install --path crates/agent-lens

Then wire it into your agent with agent-lens hook setup, or start with agent-lens help --md for the whole command surface as one Markdown document. Pre-built binaries for every tag live on the releases page.

FAQ

Questions

How is agent-lens different from a linter?

A linter tells a human how to write nicer code, one file at a time. agent-lens answers repository-scale questions an LLM asks before it edits — which functions duplicate this one, how tangled is this module, what breaks if I change this — and emits structured reports sized for a context window rather than terminal decoration.

Which languages does it analyze?

Rust, TypeScript / JavaScript, Python, and Go. Every analyzer runs on all four; the reach sections test-only and unreachable and the narrowable sections single-impl and visibility need extracted export status and judge Rust and Go only. The git-history analyzers — co-change, change-entropy and ownership — read git log rather than parsing files, so they cover anything the repository tracks. Analysis is split into a language-neutral core and per-language adapters, so adding a language means writing one adapter crate rather than reimplementing the metrics.

Do I have to use it through a coding agent?

No. Every analyzer is an ordinary CLI subcommand: agent-lens analyze <tool> <path>. The hook handlers are one way to deliver that output automatically, and agent-lens run <profile> is another for CI and pre-commit passes.

Does a failing hook block my agent?

No. Hook handlers are advisory. A handler that fails still answers in the agent's own response schema, prefixed with 'agent-lens <event> hook failed:', and exits 0 so the agent parses it. The full error goes to stderr.

Is it stable?

Not yet. The project is pre-alpha: the CLI details and report schemas are still allowed to change without a major version bump while the tool settles.

What does it cost?

Nothing. agent-lens is open source under the MIT license, distributed as a single static binary with no service, account, or telemetry attached.