Skip to content

Latest commit

 

History

History
267 lines (206 loc) · 11.8 KB

File metadata and controls

267 lines (206 loc) · 11.8 KB

Arbor Architecture

This document describes the high-level architecture of Arbor and how its components interact.

System Overview: The Unified Nervous System

Arbor is a "Unified Nervous System" that connects your codebase, AI agents, and development environment.

                    ┌─────────────────────────────────────────────────────────┐
                    │              THE UNIFIED NERVOUS SYSTEM                 │
                    └─────────────────────────────────────────────────────────┘
                                              │
          ┌───────────────────────────────────┼───────────────────────────────────┐
          │                                   │                                   │
          ▼                                   ▼                                   ▼
   ┌─────────────┐                   ┌─────────────────┐                 ┌─────────────┐
   │   VS Code   │◄──── Spotlight ──►│  Arbor Bridge   │◄── Spotlight ──►│  Visualizer │
   │  Extension  │      Protocol     │  (MCP Server)   │     Protocol    │   (Flutter) │
   └─────────────┘                   └────────┬────────┘                 └─────────────┘
          │                                   │                                   │
          │ Golden Highlight                  │ Architectural Brief              │ Camera
          │ (#FFD700)                         │ (Markdown Tables)                │ Animation
          │                                   │                                   │
          └───────────────────────────────────┼───────────────────────────────────┘
                                              │
                                     ┌────────┴────────┐
                                     │   SyncServer    │
                                     │  (WebSocket)    │
                                     │  ws://8080      │
                                     └────────┬────────┘
                                              │
                    ┌─────────────────────────┼─────────────────────────┐
                    │                         │                         │
                    ▼                         ▼                         ▼
           ┌─────────────┐           ┌─────────────┐           ┌─────────────┐
           │arbor-server │           │ arbor-graph │           │arbor-watcher│
           │  (JSON-RPC) │           │ (petgraph)  │           │  (notify)   │
           └─────────────┘           └─────────────┘           └─────────────┘
                                              │
                                              ▼
                                     ┌─────────────────┐
                                     │   arbor-core    │
                                     │  (Tree-sitter)  │
                                     │   144ms parse   │
                                     └────────┬────────┘
                                              │
                                              ▼
                                     ┌─────────────────┐
                                     │    Codebase     │
                                     │  (Your Files)   │
                                     └─────────────────┘

The Flow

  1. Parsing: arbor-core parses your codebase with Tree-sitter (~144ms for 10k lines)
  2. Graphing: arbor-graph builds a dependency graph with petgraph
  3. Watching: arbor-watcher detects file changes and triggers re-indexing
  4. Serving: arbor-server exposes the graph via JSON-RPC over WebSocket
  5. Syncing: SyncServer broadcasts real-time updates to all clients
  6. Bridging: arbor-mcp enables AI agents to query the graph
  7. Spotlighting: When AI queries a node, the Spotlight Protocol broadcasts focus events
  8. Visualizing: Flutter visualizer animates to the spotlighted node
  9. Highlighting: VS Code extension highlights the corresponding line

Crate Responsibilities

arbor-core

The foundational crate that handles parsing source files into AST nodes.

Key Components:

  • Parser: Wrapper around Tree-sitter that handles language detection and parsing
  • Language Registry: Maps file extensions to language parsers
  • Node Extraction: Traverses the AST to extract functions, classes, variables, etc.
  • Language Modules: Per-language logic for TypeScript, Rust, Python

Public API:

pub fn parse_file(path: &Path) -> Result<Vec<CodeNode>, ParseError>;
pub fn detect_language(path: &Path) -> Option<Box<dyn LanguageParser>>;

arbor-graph

Manages the in-memory graph of code relationships.

Key Components:

  • Graph: Uses petgraph for efficient graph operations
  • Schema: Defines NodeKind, EdgeType, and their attributes
  • Builder: Constructs the graph from parsed code nodes
  • Query Engine: Traversal, search, and filtering operations
  • Ranking: Centrality scoring (simplified PageRank variant)

Public API:

pub struct ArborGraph {
    pub fn add_node(&mut self, node: CodeNode) -> NodeId;
    pub fn add_edge(&mut self, from: NodeId, to: NodeId, kind: EdgeKind);
    pub fn find_by_name(&self, name: &str) -> Vec<&CodeNode>;
    pub fn get_callers(&self, id: NodeId) -> Vec<&CodeNode>;
    pub fn get_callees(&self, id: NodeId) -> Vec<&CodeNode>;
    pub fn compute_centrality(&mut self);
}

arbor-watcher

Handles file system watching and incremental updates.

Key Components:

  • Watcher: Uses notify crate to watch for file changes
  • Delta Engine: Determines what changed and what needs re-parsing
  • Cache: Stores parsed AST for unchanged files
  • Debouncer: Batches rapid file changes to avoid thrashing

Design Notes:

The watcher maintains a hash of each file's contents. When a file changes, it compares the new hash with the cached one. If different, it triggers a re-parse of only that file.

For sub-100ms updates, we use a two-tier cache:

  1. File-level cache: Stores the full AST per file
  2. Node-level cache: Stores individual nodes with their byte ranges

When a file changes, we first try a "surgical" update by re-parsing only the changed byte range. If that fails (structural change), we fall back to full file re-parse.

arbor-server

WebSocket server implementing the Arbor Protocol.

Key Components:

  • Server: Tokio-based async WebSocket server
  • Protocol Handler: Parses JSON-RPC messages
  • Query Handlers: Implements discover, impact, context, etc.
  • Subscription Manager: Handles real-time event subscriptions

Threading Model:

The server runs on a Tokio runtime with:

  • Main thread: Accepts connections
  • Per-connection task: Handles messages for that client
  • Background task: Runs the watcher and updates the graph

Graph access is protected by an RwLock. Reads are concurrent, writes are exclusive.

arbor-cli

Command-line interface for end users.

Commands:

Command Description
arbor setup One-shot onboarding (initialize + index)
arbor init Creates .arbor/ config directory
arbor index Full index of the codebase
arbor index --changed-only Incremental index based on git-changed files
arbor query <q> Search the graph
arbor diff Preview blast radius for current git changes
arbor check CI safety gate for risky change sets
arbor open <symbol> Open a symbol/file in your editor
arbor serve Start the sidecar server
arbor export Export graph to JSON
arbor status Show index status
arbor watch Re-index automatically on file changes
arbor viz Launch the Logic Forest visualizer
arbor bridge Start MCP server for AI integration
arbor bridge --viz MCP + Visualizer together
arbor doctor (check-health) System diagnostics and health check
arbor refactor <symbol> Blast-radius preview before refactor
arbor explain <symbol> Graph-backed context for AI/code reviews
arbor audit <sink> Security-oriented path tracing to sensitive sinks

Visualizer Features (v0.1.0)

The Flutter-based Arbor Visualizer includes:

  • Follow Mode: When enabled (default), the camera automatically animates to the node the AI is currently focusing on.
  • Low GPU Mode: Toggle to disable bloom and blur effects for improved performance on lower-end hardware.

Data Flow

Initial Indexing

  1. User runs arbor index
  2. CLI recursively finds all source files (respecting .gitignore)
  3. For each file:
    • Detect language from extension
    • Parse with Tree-sitter
    • Extract code nodes (functions, classes, etc.)
    • Add nodes to graph
  4. Second pass: resolve edges (calls, imports, etc.)
  5. Compute centrality scores
  6. Write graph snapshots to .arbor/graph.bin and .arbor/graph.json

Incremental Update

  1. Watcher detects file change
  2. Debouncer waits for additional changes (50ms window)
  3. Delta engine determines affected files
  4. For each affected file:
    • Remove old nodes from graph
    • Re-parse file
    • Add new nodes to graph
  5. Re-compute edges for affected nodes
  6. Update centrality (incremental approximation)

Query Handling

  1. Client sends WebSocket message
  2. Protocol handler parses JSON-RPC
  3. Query router dispatches to appropriate handler
  4. Handler queries the graph (read lock)
  5. Results are ranked and formatted
  6. JSON response sent back to client

Performance Considerations

Memory

  • Graph is stored in-memory using petgraph with adjacency list representation
  • Nodes store minimal data (id, name, kind, location)
  • Full source code is not stored; it's read from disk on demand
  • Estimated: ~1KB per node, so 100k nodes ≈ 100MB

Speed

  • Tree-sitter is extremely fast (~100k lines/second)
  • Graph operations are O(1) for lookups, O(n) for traversals
  • Centrality is computed incrementally after initial full computation
  • Debouncing prevents thrashing on rapid file changes

Concurrency

  • RwLock allows concurrent reads (queries from multiple clients)
  • Writes (graph updates) are batched and infrequent
  • Tokio provides efficient async I/O for WebSocket handling

Error Handling

Each crate defines its own error type that implements std::error::Error. Errors are propagated up the stack and converted to appropriate JSON-RPC error codes at the protocol layer.

// In arbor-core
pub enum ParseError {
    IoError(std::io::Error),
    UnsupportedLanguage(String),
    TreeSitterError(String),
}

// In arbor-server, converted to:
{
  "error": {
    "code": -32602,
    "message": "Unsupported language: xyz"
  }
}