# Contributing to Reflex

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:

1. **Speed**: Extremely fast queries on large codebases through efficient trigram indexing
2. **Accuracy**: Complete coverage with deterministic results (no probabilistic ranking)
3. **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

1. **Clone the repository:**

```bash
git clone https://github.com/reflex-search/reflex.git
cd reflex
```

2. **Build the project:**

```bash
cargo build --release
```

3. **Run tests:**

```bash
cargo test
```

4. **Run with debug logging:**

```bash
RUST_LOG=debug cargo run -- query "pattern"
```

5. **Generate documentation:**

```bash
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

1. **Create a branch** for your feature or bugfix:

```bash
git checkout -b feature/your-feature-name
# or
git checkout -b fix/issue-description
```

2. **Make your changes** following our [Code Style](#code-style) guidelines

3. **Write tests** for new functionality (see [Testing](#testing))

4. **Run the test suite:**

```bash
cargo test
```

5. **Run linters:**

```bash
cargo fmt --check  # Check formatting
cargo clippy -- -D warnings  # Run linter
```

6. **Update documentation** if adding new features or changing behavior

7. **Commit your changes** using [Conventional Commits](#conventional-commits)

8. **Push to your fork and create a Pull Request**

### Pull Request Process

1. **Open a PR** with a clear title and description
2. **Link related issues** (e.g., "Fixes #123")
3. **Wait for CI checks** to pass (tests, linting, formatting)
4. **Respond to review feedback** and make requested changes
5. **Squash commits** if requested before merge

**PR Checklist:**

- [ ] Tests pass locally (`cargo test`)
- [ ] Code is formatted (`cargo fmt`)
- [ ] 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 `rustfmt` for code formatting (run `cargo fmt` before committing)
- Use `clippy` for linting (address all warnings: `cargo clippy -- -D warnings`)
- Follow [Rust API Guidelines](https://rust-lang.github.io/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

```rust
/// 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::Result` for functions that return errors
- Use `anyhow::bail!()` for early returns with error messages
- Add context to errors using `.context()` or `.with_context()`

```rust
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:

```bash
# 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:**

```rust
#[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:

```bash
# 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`:

```bash
# 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 changes
- `refactor:` - Code refactoring (no functional changes)
- `perf:` - Performance improvements
- `test:` - Adding or updating tests
- `chore:` - Maintenance tasks (dependencies, build, etc.)
- `BREAKING CHANGE:` - Breaking change (triggers MAJOR version bump)

### Examples

```bash
# 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:

```bash
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`:

```toml
[dependencies]
tree-sitter-yourLanguage = "0.23"
```

### 2. Update Language Enum

In `src/models.rs`, add your language to the `Language` enum:

```rust
#[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`:

```rust
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:

```rust
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:

```rust
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`:

```rust
#[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](ARCHITECTURE.md#extension-guide)

---

## Debugging Tips

### Enable Debug Logging

```bash
# 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

```bash
# 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

```bash
# 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

```bash
cargo test test_name -- --nocapture --test-threads=1
```

---

## Release Process

Reflex uses **automated releases** via [release-plz](https://release-plz.ieni.dev/):

1. **Make changes** and commit using Conventional Commits
2. **Push to `main`** branch
3. **GitHub Action** automatically:
   - Analyzes commits
   - Determines next version
   - Updates `Cargo.toml` and `CHANGELOG.md`
   - Opens release PR
4. **Merge release PR** to create tag and GitHub Release

**Manual releases** are discouraged. See [CLAUDE.md Release Management](CLAUDE.md#release-management) for details.

---

## Getting Help

- **Documentation**: Start with [README.md](README.md) and [ARCHITECTURE.md](ARCHITECTURE.md)
- **Issues**: Check existing [GitHub Issues](https://github.com/reflex-search/reflex/issues)
- **Discussions**: Open a [GitHub Discussion](https://github.com/reflex-search/reflex/discussions) for questions
- **Context**: Read [CLAUDE.md](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](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! 🚀
