First off, thanks for considering contributing to Arbor. It's people like you that make this project possible.
- Code of Conduct
- Getting Started
- Development Setup
- Making Changes
- Pull Request Process
- Adding Language Support
- Style Guide
This project adheres to a Code of Conduct. By participating, you're expected to uphold this standard. Report unacceptable behavior to the maintainers.
- Bug fixes — something isn't working as expected
- New language parsers — help us support more ecosystems
- Performance improvements — make the indexer even faster
- Documentation — clarify confusing sections, add examples
- Visualizer enhancements — new shaders, interactions, layouts
- Large architectural changes without discussion first
- Vendoring dependencies unnecessarily
- Breaking changes to the protocol without a migration path
You'll need these installed:
- Rust (1.70 or later) — rustup.rs
- Flutter (3.0 or later) — flutter.dev
- Node.js (for testing TypeScript parsing) — nodejs.org
# Clone the repo
git clone https://github.com/Anandb71/arbor.git
cd arbor
# Build the Rust crates
cd crates
cargo build
# Run tests to make sure everything works
cargo test --all
# Build the visualizer (optional)
cd ../visualizer
flutter pub get
flutter build windows # or macos/linux
# Verify your environment
cd ../crates
cargo run -- check-health# Start the CLI in development mode
cd crates
cargo run --bin arbor-cli -- serve
# In another terminal, run the visualizer
cd visualizer
flutter run -d windows
# Or start everything together (MCP + Visualizer)
cargo run --bin arbor-cli -- bridge --vizUse descriptive branch names:
feat/python-parser— new featurefix/watcher-memory-leak— bug fixdocs/protocol-examples— documentationrefactor/graph-query— code cleanup
We follow conventional commits. Keep them short but descriptive:
feat(core): add Python class inheritance tracking
fix(watcher): handle symlink loops gracefully
docs: clarify WebSocket connection params
Always run the test suite before submitting:
cd crates
cargo test --all
cargo clippy --all -- -D warnings
cd ../visualizer
flutter test
flutter analyze- Fork the repo and create your branch from
main - Make your changes with appropriate tests
- Update documentation if you're changing behavior
- Run the full test suite and ensure it passes
- Submit your PR with a clear description
When you open a PR, you'll see a template. Fill it out completely — it helps us review faster.
We try to review PRs within a few days. Complex changes might take longer. If you haven't heard anything in a week, feel free to ping us.
Want to add support for a new language? Here's the process:
In crates/arbor-core/Cargo.toml:
[dependencies]
tree-sitter-your-language = "0.20"Create crates/arbor-core/src/languages/your_language.rs:
//! Parser implementation for YourLanguage.
//!
//! Handles extraction of functions, classes, and imports from
//! YourLanguage source files.
use crate::node::{CodeNode, NodeKind};
use crate::parser::LanguageParser;
use tree_sitter::Language;
pub struct YourLanguageParser;
impl LanguageParser for YourLanguageParser {
fn language(&self) -> Language {
tree_sitter_your_language::language()
}
fn extensions(&self) -> &[&str] {
&["ext1", "ext2"]
}
fn extract_nodes(&self, tree: &tree_sitter::Tree, source: &str) -> Vec<CodeNode> {
// Your extraction logic here
vec![]
}
}In crates/arbor-core/src/languages/mod.rs, add your language to the registry.
Create crates/arbor-core/tests/your_language_test.rs with representative test cases.
Add your language to the table in README.md and any relevant docs.
We follow standard Rust conventions with a few preferences:
- Use
rustfmtfor formatting (runcargo fmt) - Use
clippyfor linting (runcargo clippy) - Prefer explicit error handling over
.unwrap() - Write doc comments for public APIs
- Keep functions focused and reasonably sized
// Good: clear, documented, handles errors
/// Parses a source file and extracts all code nodes.
///
/// Returns an empty vector if the file cannot be parsed.
pub fn parse_file(path: &Path) -> Result<Vec<CodeNode>, ParseError> {
let source = fs::read_to_string(path)?;
let parser = detect_language(path)?;
Ok(parser.extract_nodes(&source))
}
// Avoid: cryptic, no docs, panics on error
pub fn parse(p: &Path) -> Vec<CodeNode> {
let s = fs::read_to_string(p).unwrap();
detect_language(p).unwrap().extract_nodes(&s)
}- Use
dart formatfor formatting - Use
flutter analyzefor linting - Follow the Effective Dart guide
- Keep widgets small and composable
- Use providers for state management (Riverpod preferred)
// Good: clear naming, single responsibility
class GraphNode extends StatelessWidget {
final NodeData data;
final VoidCallback onTap;
const GraphNode({
required this.data,
required this.onTap,
super.key,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: CustomPaint(
painter: NodePainter(data),
),
);
}
}Write comments that explain why, not what. The code shows what's happening — comments should provide context.
// Meh: obvious from the code
// Increment the counter by one
counter += 1;
// Better: explains the why
// Tree-sitter uses 0-based byte offsets, but editors expect 1-based lines
let line = byte_offset_to_line(offset) + 1;- Open a GitHub Discussion for general questions
- File an issue for bugs or feature requests
- Tag maintainers in your PR if you need guidance
Thanks for contributing. Let's build something great together.