Core Concepts
Code Health Engine
A navigability score (0–100) from cognitive complexity, naming quality and module coupling — plus the estimated token-cost tax of your hotspots. Computed once per index build on the same tree-sitter AST and fanned out across reads, edits, the gain score and CI, so cleaner code measurably lowers your AI bill.
LeanCTX compresses how code reaches the model. The Code Health Engine attacks the other half of the bill: the intrinsic cost of the code itself. Complex, cryptically named, tightly coupled code forces an agent to load more context, take more turns and re-read more often — and you pay for that confusion in tokens, on every turn that touches it. The engine turns that into a number you can watch and gate on.
It reuses the same tree-sitter AST (26 languages) that powers code intelligence, so there is no second parser and no separate index. The score is computed once per index build and persisted next to the graph, then read (never recomputed) by every surface below.
Three signals, one score
The navigability score (0–100) rolls up three measures, each grounded in the AST:
| Signal | What it measures |
|---|---|
| Cognitive complexity |
How hard a function is to follow — nesting depth, breaks in linear control flow and
tangled boolean logic. This is SonarSource's S3776 metric, not cyclomatic
count: it rewards code that reads top-to-bottom and penalises deeply nested branches.
|
| Naming quality | Cryptic, single-letter or meaningless identifiers that make an agent re-read the surrounding code just to infer what something is. A heuristic flags the worst offenders. |
| Module coupling | Afferent / efferent coupling and instability — how entangled a file is with the rest of the repo, which widens the blast radius (and the context) of any change. |
A function whose cognitive complexity crosses the threshold (default 15) is a
hotspot. Alongside the score, the engine estimates a quality tax in
USD — the recurring token cost of those hotspots, priced with the same model-pricing
table the gain report uses. "This file is messy" becomes "this
file is costing you about $X."
Compute once, fan out everywhere
The whole design follows one rule: pay the parse cost once, reuse it everywhere. On each index
build the engine scans the project, writes a small health.json next to the graph
index, and — crucially — only recomputes when the indexed source set actually changed
(fingerprint-gated, so a no-op touch never triggers a rescan).
The result is then woven into the long-term stores as a replace-source: the top
hotspots become searchable BM25 chunks and knowledge facts, and every over-threshold function
becomes a health_hotspot edge in the property graph (carrying its complexity as the
edge weight). Stale signals are pruned on every refresh, so a hotspot you fixed disappears instead
of lingering. That is what lets ctx_semantic_search find hotspots and
ctx_callgraph annotate a risky symbol with its complexity.
Where it shows up
Inline read annotations
When the agent reads code in signatures or map mode, over-threshold
functions are annotated inline — sparse (only hotspots), deterministic and a few tokens each:
fn process_request(...) · cc=23 (over)
fn validate(...) · cc=8
So the agent sees the hotspot exactly when it is about to touch it. The top hotspots are also
surfaced once in a compact session-start block, so a fresh agent knows where the
expensive code lives. Turn annotations off with [code_health] annotate_reads = false.
The edit-gate
The cheapest hotspot is the one you never create. Both ctx_edit and
ctx_patch run an edit through a complexity-delta check before writing:
⚠ code-health: process_request cognitive complexity 18 → 27 (+9, over threshold 15) The behaviour is one config knob, [code_health] gate:
| Mode | Behaviour |
|---|---|
warn (default) | Annotate the regression; let the edit through. |
block | Refuse an edit that takes a function from clean to over-threshold. |
off | Disable the gate entirely. |
Agents that edit with the host's native tools bypass ctx_edit, so the same
advisory notice is emitted from the PostToolUse hook for native Edit /
MultiEdit — the signal follows the agent regardless of edit path.
On demand — ctx_quality
ctx_quality action=report # whole-project score + hotspots + USD tax
ctx_quality action=file path=src/auth.rs # one file, function by function
ctx_quality action=delta # health change vs the last baseline
ctx_quality action=report format=json # machine-readable for CI / dashboards It is read-only and in the standard profile, so it never triggers a write-permission prompt.
In the terminal & CI — lean-ctx health
lean-ctx health # navigability score + top hotspots + quality tax
lean-ctx health src/ # scope to a path
lean-ctx health --json # machine-readable
lean-ctx health --gate # non-zero exit if the project is over its floor --gate turns "don't let the codebase get less navigable" into a scriptable CI line,
the same way doctor overhead --gate guards the context budget.
In your savings story — the gain score
Code health feeds the headline number. The gain score gains a fifth component, navigability, so a cleaner codebase visibly lifts the one number you watch:
Score: 84/100 (compression 71, cost 90, quality 76, consistency 80, navigability 84)
When no health data exists yet, the score falls back to its original four-component weighting — you
are never penalised for a signal the engine has not computed. The same complexity also surfaces on
ctx_symbol and on high-blast-radius symbols in ctx_callgraph.
Configuration
All knobs live under [code_health] in your config:
[code_health]
cognitive_threshold = 15 # complexity above which a function is a hotspot
gate = "warn" # "warn" (default) | "block" | "off"
annotate_reads = true # inline cc= annotations in signatures/map reads
naming = true # run the naming-quality heuristic Deterministic by design
Every health output is a deterministic function of (file content, mode, threshold) — no timestamps, counters or random elements in tool-output bodies. That byte-stability is what lets provider prompt caching (Anthropic up to 90%, OpenAI 50%) keep applying, so the health signal adds insight without quietly breaking the cache discount it is meant to protect.
Where to go next
- Clean Code = Lower AI Bill journey — the end-to-end story.
- Analytics & Observatory — the gain score and quality tax.
- Code Intelligence — the graph and impact tools the engine shares its AST with.
- Configuration — every
[code_health]knob.