Contributing to Reflex
May 15, 2026 · View on GitHub
Thank you for your interest in contributing to Reflex! We welcome contributions that help make Reflex faster, more accurate, and easier to use.
Project Philosophy
Reflex is built around three core principles:
- Speed: Extremely fast queries on large codebases through efficient trigram indexing
- Accuracy: Complete coverage with deterministic results (no probabilistic ranking)
- Simplicity: Clean architecture that's easy to understand, extend, and maintain
When contributing, please keep these principles in mind.
Getting Started
Prerequisites
- Rust: Version 1.75 or later (edition 2024)
Development Setup
- Clone the repository:
git clone https://github.com/reflex-search/reflex.git
cd reflex
- Build the project:
cargo build --release
- Run tests:
cargo test
- Run with debug logging:
RUST_LOG=debug cargo run -- query "pattern"
- Install git hooks (one-time setup):
# Install lefthook (pick one):
cargo install lefthook # via Cargo
# brew install lefthook # via Homebrew (macOS)
# Wire the hooks into .git/hooks/:
lefthook install
After this, any commit that includes .rs files will automatically run
cargo fmt --all and re-stage the formatted files — the commit proceeds
without any extra steps.
- Generate documentation:
cargo doc --open
Project Structure
reflex/
├── src/
│ ├── lib.rs # Library root (public API)
│ ├── main.rs # CLI entry point
│ ├── cli.rs # Command-line interface
│ ├── cache.rs # Cache management (SQLite, file I/O)
│ ├── indexer.rs # Indexing logic (trigram extraction)
│ ├── query.rs # Query engine (search execution)
│ ├── trigram.rs # Trigram algorithm (inverted index)
│ ├── content_store.rs # Memory-mapped content storage
│ ├── regex_trigrams.rs # Regex optimization
│ ├── ast_query.rs # AST pattern matching
│ ├── formatter.rs # Output formatting
│ ├── watcher.rs # File watcher (auto-reindex)
│ ├── mcp.rs # MCP server (AI integration)
│ ├── git.rs # Git integration
│ ├── models.rs # Data structures and types
│ └── parsers/ # Language-specific parsers
│ ├── mod.rs # Parser factory
│ ├── rust.rs # Rust parser
│ ├── typescript.rs # TypeScript/JavaScript parser
│ ├── python.rs # Python parser
│ ├── go.rs # Go parser
│ ├── java.rs # Java parser
│ ├── c.rs # C parser
│ ├── cpp.rs # C++ parser
│ ├── php.rs # PHP parser
│ ├── csharp.rs # C# parser
│ ├── ruby.rs # Ruby parser
│ ├── kotlin.rs # Kotlin parser
│ ├── zig.rs # Zig parser
│ ├── vue.rs # Vue parser
│ └── svelte.rs # Svelte parser
├── tests/
│ ├── integration_test.rs # End-to-end workflow tests
│ └── performance_test.rs # Performance benchmarks
├── .context/
│ └── TODO.md # Task tracking and roadmap
├── ARCHITECTURE.md # System design documentation
├── CLAUDE.md # Development workflow guide
├── README.md # User documentation
├── API.md # HTTP API reference
└── Cargo.toml # Rust package configuration
Development Workflow
Making Changes
- Create a branch for your feature or bugfix:
git checkout -b feature/your-feature-name
# or
git checkout -b fix/issue-description
-
Make your changes following our Code Style guidelines
-
Write tests for new functionality (see Testing)
-
Run the test suite:
cargo test
- Run linters:
cargo fmt --check # Verify formatting (lefthook auto-fixes on commit)
cargo clippy -- -D warnings # Run linter
-
Update documentation if adding new features or changing behavior
-
Commit your changes using Conventional Commits
-
Push to your fork and create a Pull Request
Pull Request Process
- Open a PR with a clear title and description
- Link related issues (e.g., "Fixes #123")
- Wait for CI checks to pass (tests, linting, formatting)
- Respond to review feedback and make requested changes
- Squash commits if requested before merge
PR Checklist:
- Tests pass locally (
cargo test) - Code is formatted (
cargo fmt— lefthook handles this automatically on commit) - No clippy warnings (
cargo clippy) - Documentation updated (if applicable)
- Commit messages follow Conventional Commits format
- PR description explains what/why (not just how)
Code Style
Rust Style Guidelines
Reflex follows standard Rust conventions:
- Use
rustfmtfor code formatting (runcargo fmtbefore committing) - Use
clippyfor linting (address all warnings:cargo clippy -- -D warnings) - Follow Rust API Guidelines
Naming Conventions
- Modules:
snake_case(e.g.,query_engine,ast_parser) - Structs/Enums:
PascalCase(e.g.,QueryEngine,SymbolKind) - Functions/Methods:
snake_case(e.g.,extract_symbols,parse_file) - Constants:
SCREAMING_SNAKE_CASE(e.g.,DEFAULT_TIMEOUT,MAX_FILE_SIZE)
Documentation
- Public API: All public items must have rustdoc comments
- Modules: Start with
//!module-level docs - Functions: Use
///doc comments with examples for complex functions - Examples: Include code examples in doc comments when helpful
/// Parse source code into an abstract syntax tree.
///
/// # Arguments
///
/// * `source` - Source code string
/// * `language` - Programming language
///
/// # Returns
///
/// Returns a `Tree` on success, or an error if parsing fails.
///
/// # Examples
///
/// ```
/// let tree = parse_tree("fn main() {}", Language::Rust)?;
/// ```
pub fn parse_tree(source: &str, language: Language) -> Result<Tree> {
// ...
}
Error Handling
- Use
anyhow::Resultfor functions that return errors - Use
anyhow::bail!()for early returns with error messages - Add context to errors using
.context()or.with_context()
use anyhow::{Context, Result};
fn read_file(path: &Path) -> Result<String> {
std::fs::read_to_string(path)
.with_context(|| format!("Failed to read file: {}", path.display()))
}
Testing
Reflex has comprehensive tests across three categories:
Unit Tests
Located in #[cfg(test)] modules within source files:
# Run all unit tests
cargo test --lib
# Run tests for a specific module
cargo test --lib cache::tests
# Run with output visible
cargo test -- --nocapture
Writing Unit Tests:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_trigram_extraction() {
let text = "hello world";
let trigrams = extract_trigrams(text);
assert_eq!(trigrams, vec!["hel", "ell", "llo"]);
}
}
Integration Tests
Located in tests/ directory:
# Run integration tests
cargo test --test integration_test
# Run specific integration test
cargo test --test integration_test test_full_workflow
Integration tests verify end-to-end workflows (index → query → verify).
Performance Tests
Located in tests/performance_test.rs:
# Run performance tests
cargo test --test performance_test --release
# Skip slow tests during development
cargo test --test performance_test --release -- --skip large
Performance tests ensure queries remain under target latencies.
Test Coverage Goals
- New features: Must include tests covering typical and edge cases
- Bug fixes: Add a regression test that would have caught the bug
- Parsers: Each language parser needs 8-15 tests covering all symbol types
- Core modules: Aim for >80% code coverage on critical paths
Conventional Commits
Reflex uses Conventional Commits for automatic changelog generation and version bumping.
Commit Message Format
<type>(<scope>): <description>
[optional body]
[optional footer(s)]
Commit Types
feat:- New feature (triggers MINOR version bump)fix:- Bug fix (triggers PATCH version bump)docs:- Documentation only changesrefactor:- Code refactoring (no functional changes)perf:- Performance improvementstest:- Adding or updating testschore:- Maintenance tasks (dependencies, build, etc.)BREAKING CHANGE:- Breaking change (triggers MAJOR version bump)
Examples
# Feature: Adds timeout support
git commit -m "feat(query): add --timeout flag for query timeout control"
# Bug fix: Fixes crash
git commit -m "fix(indexer): handle empty files without panic"
# Breaking change: Removes deprecated API
git commit -m "feat(api): remove deprecated /search endpoint
BREAKING CHANGE: The /search endpoint has been removed.
Use /query instead."
# Documentation
git commit -m "docs(readme): add examples for AST pattern matching"
# Refactoring
git commit -m "refactor(trigram): simplify posting list intersection"
# Performance
git commit -m "perf(query): optimize symbol lookup with hash map"
# Tests
git commit -m "test(parser): add tests for PHP enum parsing"
# Chore
git commit -m "chore(deps): update tree-sitter to 0.24.1"
Validation
Install cocogitto for local commit validation:
cargo install cocogitto
# Validate commits
cog check
# Create a conventional commit interactively
cog commit
Adding New Language Support
Reflex supports 14+ languages through Tree-sitter parsers. Here's how to add a new language:
1. Add Tree-sitter Grammar Dependency
Edit Cargo.toml:
[dependencies]
tree-sitter-yourLanguage = "0.23"
2. Update Language Enum
In src/models.rs, add your language to the Language enum:
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Language {
Rust,
Python,
// ... existing languages
YourLanguage, // Add here
}
3. Create Parser Module
Create src/parsers/your_language.rs:
use tree_sitter::{Node, Parser, Query, QueryCursor};
use crate::models::{Symbol, SymbolKind, Span};
/// Extract symbols from YourLanguage source code
pub fn extract_symbols(source: &str) -> Vec<Symbol> {
let mut parser = Parser::new();
parser.set_language(&tree_sitter_your_language::language())
.expect("Failed to load YourLanguage grammar");
let tree = match parser.parse(source, None) {
Some(tree) => tree,
None => return Vec::new(),
};
let mut symbols = Vec::new();
let root = tree.root_node();
// Extract functions
symbols.extend(extract_functions(source, root));
// Extract classes
symbols.extend(extract_classes(source, root));
// Extract other symbol types...
symbols
}
fn extract_functions(source: &str, root: Node) -> Vec<Symbol> {
// Use Tree-sitter query to find function nodes
let query_str = "(function_declaration name: (identifier) @name)";
let query = Query::new(&tree_sitter_your_language::language(), query_str)
.expect("Invalid query");
let mut cursor = QueryCursor::new();
let matches = cursor.matches(&query, root, source.as_bytes());
let mut functions = Vec::new();
for m in matches {
for capture in m.captures {
let node = capture.node;
let name = node.utf8_text(source.as_bytes()).unwrap_or("");
functions.push(Symbol {
name: name.to_string(),
kind: SymbolKind::Function,
span: Span {
start_line: node.start_position().row + 1,
start_col: node.start_position().column + 1,
end_line: node.end_position().row + 1,
end_col: node.end_position().column + 1,
},
scope: None,
});
}
}
functions
}
4. Register in Parser Factory
In src/parsers/mod.rs, add your parser:
pub mod your_language;
pub fn parse_file(source: &str, language: Language) -> Vec<Symbol> {
match language {
Language::Rust => rust::extract_symbols(source),
Language::Python => python::extract_symbols(source),
// ... existing parsers
Language::YourLanguage => your_language::extract_symbols(source),
_ => Vec::new(),
}
}
5. Update File Extensions
In src/indexer.rs, map file extensions to your language:
fn detect_language(path: &Path) -> Option<Language> {
match path.extension()?.to_str()? {
"rs" => Some(Language::Rust),
"py" => Some(Language::Python),
// ... existing extensions
"yourlang" | "yl" => Some(Language::YourLanguage),
_ => None,
}
}
6. Add Tests
Create comprehensive tests in src/parsers/your_language.rs:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_functions() {
let source = r#"
function hello() {
console.log("Hello");
}
"#;
let symbols = extract_symbols(source);
assert_eq!(symbols.len(), 1);
assert_eq!(symbols[0].name, "hello");
assert_eq!(symbols[0].kind, SymbolKind::Function);
}
// Add more tests for classes, methods, etc.
}
7. Update Documentation
- Add your language to
README.md(Supported Languages table) - Add your language to
CLAUDE.md(Supported Languages section) - Update
API.md(Supported Languages table)
Resources
- Tree-sitter Documentation: https://tree-sitter.github.io/tree-sitter/
- Tree-sitter Playground: https://tree-sitter.github.io/tree-sitter/playground
- Reflex Architecture Guide: See ARCHITECTURE.md
Debugging Tips
Enable Debug Logging
# All debug output
RUST_LOG=debug rfx query "pattern"
# Specific module
RUST_LOG=reflex::query=debug rfx query "pattern"
# Trace level (very verbose)
RUST_LOG=trace rfx query "pattern"
Profile Performance
# Build with profiling enabled
cargo build --release --features profiling
# Run with profiler (Linux)
perf record --call-graph=dwarf ./target/release/rfx query "pattern"
perf report
Inspect Cache Files
# View SQLite metadata
sqlite3 .reflex/meta.db ".schema"
sqlite3 .reflex/meta.db "SELECT * FROM files LIMIT 10;"
# View file hashes
cat .reflex/hashes.json | jq '.'
Run Single Test with Output
cargo test test_name -- --nocapture --test-threads=1
Release Process
Reflex uses automated releases via release-plz:
- Make changes and commit using Conventional Commits
- Push to
mainbranch - GitHub Action automatically:
- Analyzes commits
- Determines next version
- Updates
Cargo.tomlandCHANGELOG.md - Opens release PR
- Merge release PR to create tag and GitHub Release
Manual releases are discouraged. See CLAUDE.md Release Management for details.
Getting Help
- Documentation: Start with README.md and ARCHITECTURE.md
- Issues: Check existing GitHub Issues
- Discussions: Open a GitHub Discussion for questions
- Context: Read CLAUDE.md for project philosophy and workflow
Code of Conduct
We expect all contributors to:
- Be respectful and constructive in discussions
- Focus on technical merit and project goals
- Help create a welcoming environment for all skill levels
- Report unacceptable behavior to the maintainers
License
By contributing to Reflex, you agree that your contributions will be licensed under the MIT License.
See LICENSE for details.
Recognition
Contributors will be recognized in:
- GitHub contributor list
- Release notes for significant contributions
- CHANGELOG.md for feature additions
Thank you for contributing to Reflex! 🚀