Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .gitignore
Binary file not shown.
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,29 @@ All notable changes to Arbor will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.0] - 2026-01-07

### Added

- **C# language support** - Methods, classes, interfaces, structs, constructors, properties
- **Control Flow edges** - `FlowsTo` edge kind for CFG (Control Flow Graph) analysis
- **Data Flow edges** - `DataDependency` edge kind for DFA (Data Flow Analysis)
- **Barnes-Hut QuadTree** - O(n log n) force simulation for visualizer scalability
- **Viewport culling** - Only render visible nodes/edges for 100k+ node support
- **LOD rendering** - Simplified node rendering at low zoom levels
- **Headless mode** - `--headless` CLI flag for remote/Docker/WSL deployment
- **Binary serialization** - `bincode` dependency for future binary wire protocol

### Changed

- Consolidated language parsers into query-based `parser_v2.rs`
- Upgraded supported languages to 10 (TypeScript, JavaScript, Rust, Python, Go, Java, C, C++, Dart, C#)
- Improved graph rendering performance for large codebases

### Fixed

- None

## [0.1.1] - 2026-01-06

### Added
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ We love contributors. Whether you're fixing a typo, adding a language parser, or
- [x] **Phase 7**: Go and Java parser support
- [x] **Phase 8**: C/C++ parser support
- [x] **Phase 9**: Dart/Flutter parser support
- [ ] **Phase 10**: The Brain Upgrade (CFG & Data Flow)
- [ ] **Phase 11**: Expanded Support (C# Parser, Web-Based Visualizer)

## Security

Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Binary file not shown.

This file was deleted.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1 change: 1 addition & 0 deletions crates/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ tree-sitter-java = "0.21"
tree-sitter-c = "0.21"
tree-sitter-cpp = "0.21"
tree-sitter-dart = "0.0.4"
tree-sitter-c-sharp = "0.21"
17 changes: 13 additions & 4 deletions crates/arbor-cli/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,14 @@ pub fn query(query: &str, limit: usize) -> Result<()> {
}

/// Start the Arbor server.
pub async fn serve(port: u16, path: &Path) -> Result<()> {
println!("{}", "Starting Arbor server...".cyan());
pub async fn serve(port: u16, headless: bool, path: &Path) -> Result<()> {
let bind_addr = if headless { "0.0.0.0" } else { "127.0.0.1" };

if headless {
println!("{}", "Starting Arbor server in headless mode...".cyan());
} else {
println!("{}", "Starting Arbor server...".cyan());
}

// Index the codebase first
let result = index_directory(path)?;
Expand All @@ -147,11 +153,14 @@ pub async fn serve(port: u16, path: &Path) -> Result<()> {
result.nodes_extracted
);

let addr = format!("127.0.0.1:{}", port).parse()?;
let addr = format!("{}:{}", bind_addr, port).parse()?;
let config = ServerConfig { addr };
let server = ArborServer::new(graph, config);

println!("{} Listening on ws://127.0.0.1:{}", "✓".green(), port);
println!("{} Listening on ws://{}:{}", "✓".green(), bind_addr, port);
if headless {
println!(" Headless mode: accepting connections from any host");
}
println!(" Press {} to stop", "Ctrl+C".cyan());

server.run().await.map_err(|e| e.to_string())?;
Expand Down
10 changes: 9 additions & 1 deletion crates/arbor-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ enum Commands {
#[arg(short, long, default_value = "7432")]
port: u16,

/// Headless mode: bind to 0.0.0.0 for remote access (WSL/Docker/Server)
#[arg(long)]
headless: bool,

/// Path to index (defaults to current directory)
#[arg(default_value = ".")]
path: PathBuf,
Expand Down Expand Up @@ -125,7 +129,11 @@ async fn main() {
Commands::Init { path } => commands::init(&path),
Commands::Index { path, output } => commands::index(&path, output.as_deref()),
Commands::Query { query, limit } => commands::query(&query, limit),
Commands::Serve { port, path } => commands::serve(port, &path).await,
Commands::Serve {
port,
headless,
path,
} => commands::serve(port, headless, &path).await,
Commands::Export { output, path } => commands::export(&path, &output),
Commands::Status { path } => commands::status(&path),
Commands::Viz { path } => commands::viz(&path).await,
Expand Down
1 change: 1 addition & 0 deletions crates/arbor-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ tree-sitter-java.workspace = true
tree-sitter-c.workspace = true
tree-sitter-cpp.workspace = true
tree-sitter-dart.workspace = true
tree-sitter-c-sharp.workspace = true

[dev-dependencies]
tempfile = "3.0"
22 changes: 21 additions & 1 deletion crates/arbor-core/src/languages/dart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,29 @@ use tree_sitter::{Language, Node, Tree};

pub struct DartParser;

/// ABI Compatibility Shim for tree-sitter-dart
///
/// # Safety
/// `tree-sitter-dart` v0.0.4 is built against tree-sitter 0.20 ABI, while
/// Arbor uses tree-sitter 0.22. The ABI is compatible for our use case
/// (parsing and node traversal), but the Rust types differ.
///
/// This transmute is safe because:
/// 1. Both ABIs use the same C representation for TSLanguage
/// 2. We only use the language for parsing/tree traversal (no query syntax)
/// 3. This shim is isolated for easy removal when tree-sitter-dart updates
///
/// TODO: Remove this when tree-sitter-dart releases a 0.22+ compatible version.
#[inline]
fn dart_language_compat() -> Language {
// SAFETY: See module-level documentation above
unsafe { std::mem::transmute(tree_sitter_dart::language()) }
}

impl LanguageParser for DartParser {
fn language(&self) -> Language {
unsafe { std::mem::transmute(tree_sitter_dart::language()) }
// Use the ABI compatibility shim for tree-sitter-dart 0.0.4
dart_language_compat()
}

fn extensions(&self) -> &[&str] {
Expand Down
203 changes: 203 additions & 0 deletions crates/arbor-core/src/parser_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,33 @@ impl ArborParser {
let py_queries = Self::compile_python_queries()?;
queries.insert("py".to_string(), py_queries);

// TEMPORARILY DISABLED FOR DEBUGGING
// Compile Go queries
let go_queries = Self::compile_go_queries()?;
queries.insert("go".to_string(), go_queries);

// Compile Java queries
let java_queries = Self::compile_java_queries()?;
queries.insert("java".to_string(), java_queries);

// Compile C queries
for ext in &["c", "h"] {
queries.insert(ext.to_string(), Self::compile_c_queries()?);
}

// Compile C++ queries
for ext in &["cpp", "hpp", "cc", "hh", "cxx"] {
queries.insert(ext.to_string(), Self::compile_cpp_queries()?);
}

// NOTE: Dart queries disabled due to tree-sitter-dart 0.20 incompatibility with
// query syntax in tree-sitter 0.22. Dart is still supported via legacy parser.rs path.
// TODO: Upgrade when tree-sitter-dart releases 0.22 compatible version.

// Compile C# queries
let csharp_queries = Self::compile_csharp_queries()?;
queries.insert("cs".to_string(), csharp_queries);

Ok(Self { parser, queries })
}

Expand Down Expand Up @@ -553,6 +580,182 @@ impl ArborParser {
language,
})
}

fn compile_go_queries() -> Result<CompiledQueries> {
let language = tree_sitter_go::language();

let symbols_query = r#"
(function_declaration name: (identifier) @name) @function_def
(method_declaration name: (field_identifier) @name) @method_def
(type_declaration (type_spec name: (type_identifier) @name type: (struct_type))) @struct_def
(type_declaration (type_spec name: (type_identifier) @name type: (interface_type))) @interface_def
"#;

let imports_query = r#"
(import_spec path: (interpreted_string_literal) @source)
"#;

let calls_query = r#"
(call_expression function: (identifier) @callee)
(call_expression function: (selector_expression field: (field_identifier) @callee))
"#;

let symbols = Query::new(&language, symbols_query)
.map_err(|e| ParseError::QueryError(e.to_string()))?;
let imports = Query::new(&language, imports_query)
.map_err(|e| ParseError::QueryError(e.to_string()))?;
let calls = Query::new(&language, calls_query)
.map_err(|e| ParseError::QueryError(e.to_string()))?;

Ok(CompiledQueries {
symbols,
imports,
calls,
language,
})
}

fn compile_java_queries() -> Result<CompiledQueries> {
let language = tree_sitter_java::language();

let symbols_query = r#"
(method_declaration name: (identifier) @name) @method_def
(class_declaration name: (identifier) @name) @class_def
(interface_declaration name: (identifier) @name) @interface_def
(constructor_declaration name: (identifier) @name) @function_def
"#;

let imports_query = r#"
(import_declaration) @source
"#;

let calls_query = r#"
(method_invocation name: (identifier) @callee)
"#;

let symbols = Query::new(&language, symbols_query)
.map_err(|e| ParseError::QueryError(e.to_string()))?;
let imports = Query::new(&language, imports_query)
.map_err(|e| ParseError::QueryError(e.to_string()))?;
let calls = Query::new(&language, calls_query)
.map_err(|e| ParseError::QueryError(e.to_string()))?;

Ok(CompiledQueries {
symbols,
imports,
calls,
language,
})
}

fn compile_c_queries() -> Result<CompiledQueries> {
let language = tree_sitter_c::language();

let symbols_query = r#"
(function_definition declarator: (function_declarator declarator: (identifier) @name)) @function_def
(struct_specifier name: (type_identifier) @name) @struct_def
(enum_specifier name: (type_identifier) @name) @enum_def
"#;

let imports_query = r#"
(preproc_include path: (string_literal) @source)
(preproc_include path: (system_lib_string) @source)
"#;

let calls_query = r#"
(call_expression function: (identifier) @callee)
"#;

let symbols = Query::new(&language, symbols_query)
.map_err(|e| ParseError::QueryError(e.to_string()))?;
let imports = Query::new(&language, imports_query)
.map_err(|e| ParseError::QueryError(e.to_string()))?;
let calls = Query::new(&language, calls_query)
.map_err(|e| ParseError::QueryError(e.to_string()))?;

Ok(CompiledQueries {
symbols,
imports,
calls,
language,
})
}

fn compile_cpp_queries() -> Result<CompiledQueries> {
let language = tree_sitter_cpp::language();

let symbols_query = r#"
(function_definition declarator: (function_declarator declarator: (identifier) @name)) @function_def
(function_definition declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name))) @method_def
(class_specifier name: (type_identifier) @name) @class_def
(struct_specifier name: (type_identifier) @name) @struct_def
"#;

let imports_query = r#"
(preproc_include path: (string_literal) @source)
(preproc_include path: (system_lib_string) @source)
"#;

let calls_query = r#"
(call_expression function: (identifier) @callee)
(call_expression function: (field_expression field: (field_identifier) @callee))
"#;

let symbols = Query::new(&language, symbols_query)
.map_err(|e| ParseError::QueryError(e.to_string()))?;
let imports = Query::new(&language, imports_query)
.map_err(|e| ParseError::QueryError(e.to_string()))?;
let calls = Query::new(&language, calls_query)
.map_err(|e| ParseError::QueryError(e.to_string()))?;

Ok(CompiledQueries {
symbols,
imports,
calls,
language,
})
}

fn compile_csharp_queries() -> Result<CompiledQueries> {
let language = tree_sitter_c_sharp::language();

let symbols_query = r#"
(method_declaration name: (identifier) @name) @method_def
(class_declaration name: (identifier) @name) @class_def
(interface_declaration name: (identifier) @name) @interface_def
(struct_declaration name: (identifier) @name) @struct_def
(constructor_declaration name: (identifier) @name) @function_def
(property_declaration name: (identifier) @name) @method_def
"#;

let imports_query = r#"
(using_directive (identifier) @source)
(using_directive (qualified_name) @source)
"#;

let calls_query = r#"
(invocation_expression function: (identifier) @callee)
(invocation_expression function: (member_access_expression name: (identifier) @callee))
"#;

let symbols = Query::new(&language, symbols_query)
.map_err(|e| ParseError::QueryError(e.to_string()))?;
let imports = Query::new(&language, imports_query)
.map_err(|e| ParseError::QueryError(e.to_string()))?;
let calls = Query::new(&language, calls_query)
.map_err(|e| ParseError::QueryError(e.to_string()))?;

Ok(CompiledQueries {
symbols,
imports,
calls,
language,
})
}

// NOTE: compile_dart_queries() removed - tree-sitter-dart 0.20 is incompatible
// with tree-sitter 0.22 query syntax. Dart is still supported via legacy parser.rs.
// TODO: Add Dart query support when tree-sitter-dart releases 0.22+ compatible version.
}

// ─────────────────────────────────────────────────────────────────────────────
Expand Down
10 changes: 10 additions & 0 deletions crates/arbor-graph/src/edge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ pub enum EdgeKind {

/// Container relationship (class contains method).
Contains,

/// Control flow: statement A flows to statement B.
/// Used for CFG (Control Flow Graph) edges.
FlowsTo,

/// Data dependency: variable use depends on definition.
/// Used for DFA (Data Flow Analysis) edges.
DataDependency,
}

impl std::fmt::Display for EdgeKind {
Expand All @@ -42,6 +50,8 @@ impl std::fmt::Display for EdgeKind {
Self::UsesType => "uses_type",
Self::References => "references",
Self::Contains => "contains",
Self::FlowsTo => "flows_to",
Self::DataDependency => "data_dependency",
};
write!(f, "{}", s)
}
Expand Down
1 change: 1 addition & 0 deletions crates/arbor-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ futures-util = "0.3"
uuid = { version = "1.0", features = ["v4"] }
chrono = "0.4"
notify = "6.1"
bincode = "1.3"

Binary file added crates/test_output.txt
Binary file not shown.
Loading
Loading