Skip to content

v2.6.0 — Telling the truth about the graph

Latest

Choose a tag to compare

@Anandb71 Anandb71 released this 03 Aug 02:02
· 1 commit to main since this release
49a473a

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 list

In 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

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.0

Assets

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.0

Assets

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.0

Assets

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