Nine correctness defects in the analysis core. Every one was found by running
Arbor against real codebases — honojs/hono, psf/requests — rather than
fixtures, and every one ships with a regression test.
Zero breaking API changes. cargo install arbor-graph-cli --force and the
answers get more accurate.
The graph was lying in six ways
Determinism was not deterministic. Symbol resolution decided
same-directory locality by iterating a HashMap and taking the first hit.
Rust seeds RandomState per process, so the same binary on the same input
could build different edges between runs. A test now indexes a fixture eight
times in separate processes and asserts one identical graph — the only way
to catch this, since a single process shares one hash seed.
Colliding symbols were silently dropped. The FQN map used
HashMap::insert, so a second definition of handler, new, or process
replaced the first. The loser became unreachable — zero callers, zero
centrality, invisible to blast radius. Every definition is now kept.
Ambiguous lookups answered confidently and wrongly. On hono,
arbor inspect getPath reported "unreachable, 0 callers, may be dead code"
about a function called from ten places, because eleven call sites across the
CLI and MCP bridge resolved names with find_by_name(..).first() — whichever
file parsed first. There is now one ranked resolution policy, and the CLI
tells you which definition it picked and what else matched.
TypeScript symbols were extracted twice. The export_statement handler
recursed into its children, then the generic loop recursed into the same
children again. Every exported symbol became two vertices sharing one node id.
On a 149-file Next.js app: 133 phantom nodes, 25% of the graph, each halving
its symbol's centrality and double-counting it in blast radius.
Prose was indexed as code. Markdown headings became graph nodes, so a
# fetch benchmark heading in a README competed with real fetch definitions
during resolution. Shell comments became functions — # Compare app.fetch() between refs yielded a function literally named "# Compare app.fetch". 59
phantom nodes on hono, every one a false resolution candidate.
Centrality was not comparable between repositories. Scores were divided by
the graph maximum, so the top node was 1.0 by construction whether it had
four callers or four hundred. A 0.6 threshold selected nearly everything in
a flat repo and nothing in one with a god object, and adding a single hub
rescaled every other node. Centrality is now a percentile rank.
Recall on TypeScript
obj.method() was discarded outright, on the reasoning that typing the
receiver needs inference we do not have. That is the dominant call shape in
real TS/JS, so the graph came out nearly edgeless on the largest ecosystem
Arbor supports — and an empty graph reports a blast radius of zero, which
reads as safe rather than unknown. Those calls now resolve by method name
under a fan-out cap, at reduced confidence.
Measured on identical node sets, after the duplicate fix:
| before | after | |
|---|---|---|
| TypeScript (149 files) | 172 edges | 196 (+14%) |
| Rust (arbor-graph) | 116 edges | 167 (+44%) |
New
Edge confidence. Every edge carries a value in [0,1] derived from how it
resolved, so traversal no longer treats a proven same-file call and a
heuristic suffix guess as identical evidence. confident_edge_count() reports
how much of a graph rests on inference.
Hunk-level impact. changed_node_ids_for_ranges maps diff line ranges to
the symbols that actually overlap them, and parse_unified_diff_ranges reads
them from a patch. File granularity treats a one-line edit in a 60-symbol file
as changing all 60.
Concept search. Identifiers are tokenized — snake, camel, Pascal, acronym
runs — and expanded through curated concept clusters, so login finds
get_authenticated, which shares no substring with it and was previously
unfindable. Docstrings, signatures, and file paths are indexed too; they were
parsed into every node and then ignored. Results carry the match kind, so an
exact hit stays distinguishable from a concept guess.
Upgrading
Graph caches are invalidated automatically — the serialized format gained edge
confidence and raw centrality, and CACHE_VERSION tracks the package version.
The first index after upgrading rebuilds from source.
320 tests, clippy clean at -D warnings, verified end to end through the CLI
and the MCP bridge.
🤖 Arbor MCP Quick Install
Use Arbor as a local MCP server in your AI client:
Claude Code (project-scoped)
claude mcp add --transport stdio --scope project arbor -- arbor bridge
claude mcp listIn Claude Code, run:
/mcp
Cursor MCP config (.cursor/mcp.json)
{
"mcpServers": {
"arbor": {
"command": "arbor",
"args": ["bridge"]
}
}
}VS Code MCP config (.vscode/mcp.json)
{
"servers": {
"arbor": {
"type": "stdio",
"command": "arbor",
"args": ["bridge"]
}
},
"inputs": []
}MCP Directories
- Glama: https://glama.ai/mcp/servers/@Anandb71/arbor
- Skills Playground: https://skillsplayground.com/mcps/nandb71-arbor/
Arbor v2.6.0
Installation
# Cargo
cargo install arbor-graph-cli --version 2.6.0
# Homebrew (macOS/Linux)
brew install Anandb71/tap/arbor
# Scoop (Windows)
scoop bucket add arbor https://github.com/Anandb71/arbor
scoop install arbor
# npm
npx @anandb71/arbor-cli
# Docker
docker pull ghcr.io/anandb71/arbor:v2.6.0Assets
| Platform | Architecture | File |
|---|---|---|
| Linux | x86_64 | arbor-linux-x86_64.tar.gz |
| Linux | aarch64 | arbor-linux-aarch64.tar.gz |
| macOS | x86_64 | arbor-macos-x86_64.tar.gz |
| macOS | Apple Silicon | arbor-macos-aarch64.tar.gz |
| Windows | x86_64 | arbor-windows-x86_64.zip |
What's Changed
- docs: replace hero GIF with grep-vs-arbor side-by-side demo by @Anandb71 in #159
- chore: v2.5.0 Homebrew/Scoop checksums by @Anandb71 in #161
- Fix/graph truth layer by @Anandb71 in #162
- fix(graph): correct symbol resolution, centrality, and edge recall (v2.6.0) by @Anandb71 in #163
Full Changelog: v2.5.0...v2.6.0
Arbor v2.6.0
Installation
# Cargo
cargo install arbor-graph-cli --version 2.6.0
# Homebrew (macOS/Linux)
brew install Anandb71/tap/arbor
# Scoop (Windows)
scoop bucket add arbor https://github.com/Anandb71/arbor
scoop install arbor
# npm
npx @anandb71/arbor-cli
# Docker
docker pull ghcr.io/anandb71/arbor:v2.6.0Assets
| Platform | Architecture | File |
|---|---|---|
| Linux | x86_64 | arbor-linux-x86_64.tar.gz |
| Linux | aarch64 | arbor-linux-aarch64.tar.gz |
| macOS | x86_64 | arbor-macos-x86_64.tar.gz |
| macOS | Apple Silicon | arbor-macos-aarch64.tar.gz |
| Windows | x86_64 | arbor-windows-x86_64.zip |
What's Changed
- docs: replace hero GIF with grep-vs-arbor side-by-side demo by @Anandb71 in #159
- chore: v2.5.0 Homebrew/Scoop checksums by @Anandb71 in #161
- Fix/graph truth layer by @Anandb71 in #162
- fix(graph): correct symbol resolution, centrality, and edge recall (v2.6.0) by @Anandb71 in #163
- chore(release): sync packaging manifests to 2.6.0 by @Anandb71 in #164
Full Changelog: v2.5.0...v2.6.0
Arbor v2.6.0
Installation
# Cargo
cargo install arbor-graph-cli --version 2.6.0
# Homebrew (macOS/Linux)
brew install Anandb71/tap/arbor
# Scoop (Windows)
scoop bucket add arbor https://github.com/Anandb71/arbor
scoop install arbor
# npm
npx @anandb71/arbor-cli
# Docker
docker pull ghcr.io/anandb71/arbor:v2.6.0Assets
| Platform | Architecture | File |
|---|---|---|
| Linux | x86_64 | arbor-linux-x86_64.tar.gz |
| Linux | aarch64 | arbor-linux-aarch64.tar.gz |
| macOS | x86_64 | arbor-macos-x86_64.tar.gz |
| macOS | Apple Silicon | arbor-macos-aarch64.tar.gz |
| Windows | x86_64 | arbor-windows-x86_64.zip |
What's Changed
- docs: replace hero GIF with grep-vs-arbor side-by-side demo by @Anandb71 in #159
- chore: v2.5.0 Homebrew/Scoop checksums by @Anandb71 in #161
- Fix/graph truth layer by @Anandb71 in #162
- fix(graph): correct symbol resolution, centrality, and edge recall (v2.6.0) by @Anandb71 in #163
- chore(release): sync packaging manifests to 2.6.0 by @Anandb71 in #164
Full Changelog: v2.5.0...v2.6.0