Deterministic analysis
Identical trees produce byte-identical graphs. That is what makes assess_change usable: every delta it reports is a real change in the code,
not parser jitter between two runs.
MCP · stdio · read-only
Agents have a specific economics: every token of context costs, and the global view never fits. An agent that re-reads five files to work out what calls a function has spent its budget rebuilding something a graph already knows. nao serves that graph as compact ranked text — capped, explicitly truncated, filtered of noise.
The loop
This is the shape of a task that goes well. Every step replaces something the agent would otherwise do by reading files and inferring.
overviewWhich Feature does this task belong to, and which files does it claim? Skipped when the project has no .elv layer.
mapGet the folder’s shape instead of reading ten files to reconstruct it.
similarCheck the helper does not already exist under another name.
contextPull the edit target plus its neighbours’ signatures in one call.
impact · traceWho breaks if this changes, and how control arrives here.
Ordinary edits, with LSP for the precise pointwise work. nao does not replace that layer.
tests_forRun the tests that actually reach the change, not the whole suite.
assess_changeMetric deltas and smell churn on the real diff, before presenting it.
The surface
| Tool | Answers |
|---|---|
| overview | The Elevator domain map, or a focused bundle for one entity. The grounding step — which capability does this task belong to. |
| map | Files and entities of a folder with complexity and coupling annotations, at three depth levels. One call instead of ten file reads. |
| similar | Similarity-ranked search before you write something that already exists. |
| context | A minimal edit pack: the target’s source plus the signatures of everything it uses and everything using it. |
| impact | Blast radius — what it uses, direct dependents, then transitive dependents level by level. |
| trace | Shortest dependency path(s) from A to B, with the relationship kind of every hop. |
| quality | Smells, top offenders by composite refactor pressure, dependency cycles. |
| hotspots | Git churn × complexity. The ranking complexity alone cannot give you. |
| tests_for | Which tests reach an entity, directly or transitively — including Rust inline mod tests. |
| assess_change | Working tree versus a git ref: per-entity metric deltas, added and removed entities, smell churn. |
Why the output is trustworthy
Identical trees produce byte-identical graphs. That is what makes assess_change usable: every delta it reports is a real change in the code,
not parser jitter between two runs.
UsesType edges come straight out of signatures and fields in Rust,
TypeScript, Svelte, Java, Kotlin and Python — so type-level impact is exact rather than
inferred in those five.
93.7% recall at 98.6% precision against a rust-analyzer oracle. Good enough to act on — not good enough to treat an empty result as proof.
Graphs are cached per path with watcher-driven invalidation. The first call after a source change re-analyses; the rest are milliseconds.
The full limitations, including what the parsers still miss →
Setup
nao mcp speaks MCP over stdio. Drop it in your client's server config — .mcp.json at the project root for Claude Code, the equivalent block for any
other MCP client — and the tools appear.
There is no daemon to run, no port to pick and nothing to authenticate against. The server reads the checkout it was pointed at and nothing else.
Install first →{
"mcpServers": {
"nao": {
"command": "nao",
"args": ["mcp"]
}
}
}“Use nao's overview and map before you read any files. Then use impact on the function you plan to change, and assess_change before you show me the diff.”