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.
wrapper
Functions whose body is a forwarding call modulo a short chain of ?, .unwrap(), .into(), .await.
delegation
Chains that only forward — 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.
single-use
Functions with exactly one resolved production caller, small and simple enough to inline into it — with caveats where the claim is weaker, and a calibration section for setting the thresholds per repository.
single-impl
Traits and interfaces with at most one production implementor — candidates for the concrete type, with mock-seam, dyn-dispatch and visibility caveats, and the tree's implementor-count histogram.

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.
graph-query
One canned traversal per run — callers, callees, neighborhood, or the shortest path between two symbols.

Reachability

Code nothing runs, nothing tests, or nothing outside the module needs.

untested
Production functions with no resolved call path from any test, grouped by module and ranked by untested LOC.
unreachable
Functions no entry point reaches, in confidence tiers — confirmed rows are deletable on that evidence alone (Rust, Go).
test-only
Production functions only tests keep alive — candidates to move into test scope or delete with their tests (Rust, Go).
visibility
pub or exported functions whose callers all sit inside a narrower scope, with the declaration that would still compile (Rust, Go).

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.

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 unreachable, visibility
Pythonruff_python_parserAll but unreachable, visibility
Gotree-sitterEvery analyzer

unreachable and visibility need extracted export status, which TypeScript and Python do not carry — so those two are wired through the Rust and Go adapters 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 except unreachable and visibility, which need extracted export status and are wired through the Rust and Go adapters only. The git-history analyzers — co-change and change-entropy — 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.