Skip to content

Contributing

rUv edited this page Jul 31, 2025 · 1 revision

Contributing

Welcome to FACT! This guide will help you contribute to the Fast Augmented Context Tools project across Python, Rust, WASM, and NPM implementations.

🚀 Getting Started

Prerequisites

Ensure you have the required tools installed:

# Python development
python >= 3.8
pip >= 21.0
poetry >= 1.2.0  # Optional but recommended

# Rust development  
rustc >= 1.70.0
cargo >= 1.70.0
wasm-pack >= 0.12.0  # For WASM builds

# Node.js development
node >= 16.0.0
npm >= 8.0.0
# or
yarn >= 1.22.0

# Git
git >= 2.20.0

Development Setup

# Clone the repository
git clone https://github.com/ruvnet/FACT.git
cd FACT

# Set up Python environment
python -m venv fact-env
source fact-env/bin/activate  # On Windows: fact-env\Scripts\activate

# Install Python dependencies
pip install -e ".[dev]"

# Set up Rust environment (if contributing to Rust components)
cd rust-fact
cargo build

# Set up Node.js environment (if contributing to NPM package)
cd npm-fact
npm install

# Run tests to verify setup
python -m pytest                    # Python tests
cargo test                          # Rust tests  
npm test                            # Node.js tests

🏗️ Project Structure

Understanding the project layout helps you navigate and contribute effectively:

FACT/
├── fact/                          # Python package
│   ├── __init__.py
│   ├── driver.py                  # Main FACT driver
│   ├── cache.py                   # Caching system
│   ├── templates.py               # Template engine
│   └── security.py                # Security components
├── rust-fact/                     # Rust implementation
│   ├── src/
│   │   ├── lib.rs
│   │   ├── engine.rs              # Core engine
│   │   ├── cache.rs               # Cache implementation
│   │   └── processor.rs           # Query processor
│   └── Cargo.toml
├── wasm-fact/                     # WASM bindings
│   ├── src/
│   │   └── lib.rs
│   └── pkg/                       # Generated WASM output
├── npm-fact/                      # NPM package
│   ├── src/
│   │   ├── index.js
│   │   ├── driver.js
│   │   └── cache.js
│   └── package.json
├── tests/                         # Test suites
│   ├── python/
│   ├── rust/
│   └── integration/
├── docs/                          # Documentation
├── examples/                      # Usage examples
└── wiki/                          # GitHub wiki content

🎯 Contribution Types

1. Bug Reports

When reporting bugs, please include:

## Bug Report

**Environment:**
- FACT version: 
- Python/Rust/Node version:
- Operating System:

**Description:**
Brief description of the issue

**Steps to Reproduce:**
1. Step one
2. Step two
3. Step three

**Expected Behavior:**
What should happen

**Actual Behavior:**
What actually happens

**Error Messages:**

Include any error messages or stack traces


**Additional Context:**
Any other relevant information

2. Feature Requests

For new features, please provide:

## Feature Request

**Problem Statement:**
What problem does this solve?

**Proposed Solution:**
Describe your proposed approach

**Alternatives Considered:**
What other approaches did you consider?

**Implementation Notes:**
Technical details about implementation

**Impact:**
- Performance impact
- Breaking changes
- Documentation needs

3. Code Contributions

Python Contributions

# Follow PEP 8 style guidelines
# Use type hints where possible
# Include docstrings for public functions

from typing import Optional, Dict, Any
import asyncio

async def process_query(
    query: str, 
    context: Optional[Dict[str, Any]] = None
) -> str:
    """
    Process a user query with optional context.
    
    Args:
        query: The user query string
        context: Optional context dictionary
        
    Returns:
        Processed query result
        
    Raises:
        ValidationError: If query is invalid
        ProcessingError: If processing fails
    """
    # Implementation here
    pass

Rust Contributions

// Follow Rust conventions and use clippy
// Include comprehensive documentation
// Use proper error handling with Result types

use std::collections::HashMap;
use serde::{Deserialize, Serialize};

/// Configuration for the FACT engine
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EngineConfig {
    /// Maximum cache size in bytes
    pub max_cache_size: usize,
    /// Default TTL for cache entries in seconds
    pub default_ttl: u64,
}

impl EngineConfig {
    /// Create a new engine configuration with defaults
    pub fn new() -> Self {
        Self {
            max_cache_size: 100_000_000, // 100MB
            default_ttl: 3600,           // 1 hour
        }
    }
}

/// Process a query using the FACT engine
pub async fn process_query(
    query: &str,
    config: &EngineConfig,
) -> Result<String, ProcessingError> {
    // Implementation here
    todo!()
}

JavaScript/TypeScript Contributions

/**
 * FACT driver for Node.js and browser environments
 * @module FACTDriver
 */

/**
 * Configuration options for FACT driver
 * @typedef {Object} DriverConfig
 * @property {number} maxCacheSize - Maximum cache size in MB
 * @property {number} defaultTTL - Default TTL in seconds
 * @property {boolean} enableMetrics - Enable performance metrics
 */

/**
 * Process a query with FACT
 * @param {string} query - The query to process
 * @param {Object} [context] - Optional context object
 * @param {DriverConfig} [config] - Configuration options
 * @returns {Promise<string>} The processed result
 * @throws {ValidationError} When query is invalid
 * @throws {ProcessingError} When processing fails
 */
async function processQuery(query, context = {}, config = {}) {
    // Validation
    if (typeof query !== 'string' || query.trim().length === 0) {
        throw new ValidationError('Query must be a non-empty string');
    }
    
    // Implementation here
}

module.exports = { processQuery };

🧪 Testing Guidelines

Python Testing

# Use pytest for Python tests
# Include unit tests, integration tests, and property-based tests

import pytest
from unittest.mock import Mock, patch
from fact.driver import FACTDriver
from fact.exceptions import ValidationError

class TestFACTDriver:
    """Test suite for FACT driver"""
    
    @pytest.fixture
    def driver(self):
        """Create a test driver instance"""
        return FACTDriver(config={'cache_size': 1000})
    
    @pytest.mark.asyncio
    async def test_process_query_success(self, driver):
        """Test successful query processing"""
        query = "What is the capital of France?"
        result = await driver.process_query(query)
        
        assert isinstance(result, str)
        assert len(result) > 0
    
    @pytest.mark.asyncio
    async def test_process_query_validation_error(self, driver):
        """Test query validation error"""
        with pytest.raises(ValidationError):
            await driver.process_query("")
    
    @pytest.mark.parametrize("query,expected", [
        ("simple query", "simple response"),
        ("complex query with context", "detailed response"),
    ])
    async def test_process_query_parameterized(self, driver, query, expected):
        """Test query processing with parameters"""
        with patch.object(driver, '_process_internal') as mock_process:
            mock_process.return_value = expected
            result = await driver.process_query(query)
            assert result == expected

Rust Testing

// Use built-in Rust testing framework
// Include unit tests, integration tests, and property tests

#[cfg(test)]
mod tests {
    use super::*;
    use tokio_test;
    
    #[tokio::test]
    async fn test_process_query_success() {
        let config = EngineConfig::new();
        let query = "What is the capital of France?";
        
        let result = process_query(query, &config).await;
        
        assert!(result.is_ok());
        assert!(!result.unwrap().is_empty());
    }
    
    #[tokio::test]
    async fn test_process_query_empty_input() {
        let config = EngineConfig::new();
        let query = "";
        
        let result = process_query(query, &config).await;
        
        assert!(result.is_err());
        match result.unwrap_err() {
            ProcessingError::ValidationError(_) => {},
            _ => panic!("Expected ValidationError"),
        }
    }
    
    #[test]
    fn test_engine_config_defaults() {
        let config = EngineConfig::new();
        
        assert_eq!(config.max_cache_size, 100_000_000);
        assert_eq!(config.default_ttl, 3600);
    }
}

// Property-based testing with proptest
#[cfg(test)]
mod property_tests {
    use super::*;
    use proptest::prelude::*;
    
    proptest! {
        #[test]
        fn test_query_processing_never_panics(
            query in "\\PC*",
            cache_size in 1000u64..10_000_000u64
        ) {
            let config = EngineConfig {
                max_cache_size: cache_size as usize,
                default_ttl: 3600,
            };
            
            // This should never panic, regardless of input
            let runtime = tokio::runtime::Runtime::new().unwrap();
            let result = runtime.block_on(process_query(&query, &config));
            
            // We don't care about the result, just that it doesn't panic
            drop(result);
        }
    }
}

JavaScript Testing

// Use Jest for JavaScript testing
// Include unit tests, integration tests, and snapshot tests

const { processQuery } = require('../src/driver');
const { ValidationError, ProcessingError } = require('../src/errors');

describe('FACT Driver', () => {
    describe('processQuery', () => {
        test('should process valid query successfully', async () => {
            const query = 'What is the capital of France?';
            const result = await processQuery(query);
            
            expect(typeof result).toBe('string');
            expect(result.length).toBeGreaterThan(0);
        });
        
        test('should throw ValidationError for empty query', async () => {
            await expect(processQuery('')).rejects.toThrow(ValidationError);
        });
        
        test('should throw ValidationError for non-string query', async () => {
            await expect(processQuery(123)).rejects.toThrow(ValidationError);
        });
        
        test('should handle context object', async () => {
            const query = 'What is the weather like?';
            const context = { location: 'Paris', units: 'metric' };
            
            const result = await processQuery(query, context);
            
            expect(typeof result).toBe('string');
        });
        
        test('should respect configuration options', async () => {
            const query = 'Test query';
            const config = { maxCacheSize: 50, defaultTTL: 1800 };
            
            const result = await processQuery(query, {}, config);
            
            expect(typeof result).toBe('string');
        });
    });
    
    describe('error handling', () => {
        test('should handle network errors gracefully', async () => {
            // Mock network failure
            const originalFetch = global.fetch;
            global.fetch = jest.fn().mockRejectedValue(new Error('Network error'));
            
            await expect(processQuery('test query')).rejects.toThrow(ProcessingError);
            
            global.fetch = originalFetch;
        });
    });
});

// Integration tests
describe('Integration Tests', () => {
    test('should work end-to-end with real data', async () => {
        const query = 'List the top 5 tech companies by market cap';
        
        const result = await processQuery(query, {
            format: 'json',
            limit: 5
        });
        
        expect(result).toMatchSnapshot();
    });
});

📏 Code Quality Standards

Linting and Formatting

# Python
black --line-length 88 fact/           # Format code
isort fact/                            # Sort imports  
flake8 fact/                          # Linting
mypy fact/                            # Type checking

# Rust
cargo fmt                             # Format code
cargo clippy -- -D warnings          # Linting
cargo audit                          # Security audit

# JavaScript
npx prettier --write src/             # Format code
npx eslint src/                      # Linting
npx jshint src/                      # Additional linting

Pre-commit Hooks

Set up pre-commit hooks to ensure code quality:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/psf/black
    rev: 23.7.0
    hooks:
      - id: black
        language_version: python3.8
  
  - repo: https://github.com/pycqa/isort
    rev: 5.12.0
    hooks:
      - id: isort
  
  - repo: https://github.com/pycqa/flake8
    rev: 6.0.0
    hooks:
      - id: flake8
  
  - repo: https://github.com/doublify/pre-commit-rust
    rev: v1.0
    hooks:
      - id: fmt
      - id: clippy
  
  - repo: https://github.com/pre-commit/mirrors-prettier
    rev: v3.0.0
    hooks:
      - id: prettier
        types_or: [javascript, json, yaml]

🔄 Development Workflow

Branch Strategy

# Main branches
main          # Production-ready code
develop       # Integration branch for features

# Feature branches
feature/      # New features
bugfix/       # Bug fixes
hotfix/       # Critical fixes for production
refactor/     # Code refactoring
docs/         # Documentation updates

Commit Message Format

Follow conventional commits:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

Examples:

feat(cache): add multi-tier caching support
fix(security): resolve authentication bypass vulnerability
docs(api): update query processing documentation
test(rust): add property-based tests for engine
refactor(python): improve error handling in driver
perf(wasm): optimize memory usage in browser builds

Pull Request Process

  1. Create Feature Branch
git checkout -b feature/add-new-template-type
  1. Make Changes
  • Write code following style guidelines
  • Add comprehensive tests
  • Update documentation
  • Ensure all tests pass
  1. Create Pull Request
## Description
Brief description of changes

## Changes Made
- [ ] Added new template type support
- [ ] Updated documentation
- [ ] Added test coverage

## Testing
- [ ] Unit tests pass
- [ ] Integration tests pass
- [ ] Performance tests pass

## Breaking Changes
None / List any breaking changes

## Related Issues
Closes #123
  1. Code Review Process
  • At least one reviewer approval required
  • All CI checks must pass
  • Address review feedback
  • Squash commits before merge if requested

📊 Performance Considerations

Benchmarking

# Python benchmarking example
import time
import asyncio
from fact.driver import FACTDriver

async def benchmark_query_processing():
    """Benchmark query processing performance"""
    driver = FACTDriver()
    queries = [
        "Simple query",
        "Complex analytical query with multiple parameters",
        "Query requiring external data processing"
    ]
    
    for query in queries:
        start_time = time.time()
        result = await driver.process_query(query)
        end_time = time.time()
        
        print(f"Query: {query[:30]}...")
        print(f"Time: {end_time - start_time:.3f}s")
        print(f"Result length: {len(result)}")
        print("-" * 50)

if __name__ == "__main__":
    asyncio.run(benchmark_query_processing())
// Rust benchmarking with criterion
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use fact_engine::process_query;

fn benchmark_query_processing(c: &mut Criterion) {
    let runtime = tokio::runtime::Runtime::new().unwrap();
    let config = EngineConfig::new();
    
    c.bench_function("simple query", |b| {
        b.iter(|| {
            runtime.block_on(process_query(
                black_box("What is the capital of France?"),
                black_box(&config),
            ))
        })
    });
    
    c.bench_function("complex query", |b| {
        b.iter(|| {
            runtime.block_on(process_query(
                black_box("Analyze market trends for tech stocks in Q3 2024"),
                black_box(&config),
            ))
        })
    });
}

criterion_group!(benches, benchmark_query_processing);
criterion_main!(benches);

📚 Documentation Standards

Code Documentation

def process_template(
    template_name: str,
    variables: Dict[str, Any],
    config: Optional[TemplateConfig] = None
) -> str:
    """
    Process a template with given variables.
    
    This function applies the specified template to the provided variables,
    performing any necessary transformations and validations.
    
    Args:
        template_name: Name of the template to use. Must be registered
            in the template registry.
        variables: Dictionary of variables to substitute in the template.
            Keys should match template placeholders.
        config: Optional template configuration. If None, uses default
            configuration.
    
    Returns:
        Processed template as a string with variables substituted.
    
    Raises:
        TemplateNotFoundError: If the specified template doesn't exist.
        ValidationError: If required variables are missing or invalid.
        ProcessingError: If template processing fails.
    
    Example:
        >>> process_template(
        ...     "greeting",
        ...     {"name": "Alice", "time": "morning"},
        ...     TemplateConfig(strict_mode=True)
        ... )
        "Good morning, Alice!"
    
    Note:
        Template processing is cached for performance. The cache key
        includes the template name and a hash of the variables.
    
    See Also:
        register_template: For adding new templates
        TemplateConfig: For configuration options
    """
    pass

API Documentation

Use OpenAPI/Swagger for REST API documentation:

# openapi.yaml
openapi: 3.0.3
info:
  title: FACT API
  description: Fast Augmented Context Tools API
  version: 1.0.0

paths:
  /query:
    post:
      summary: Process a query
      description: |
        Process a natural language query using FACT's AI-powered
        context processing capabilities.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - query
              properties:
                query:
                  type: string
                  description: The query to process
                  example: "What are the top 5 tech companies?"
                context:
                  type: object
                  description: Optional context for the query
                  example:
                    format: "json"
                    limit: 5
      responses:
        '200':
          description: Query processed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: string
                    description: The processed query result
                  cached:
                    type: boolean
                    description: Whether result was served from cache
                  processing_time:
                    type: number
                    description: Processing time in milliseconds

🛡️ Security Guidelines

Secure Coding Practices

# Input validation
def validate_query_input(query: str, max_length: int = 10000) -> str:
    """Validate and sanitize query input"""
    if not isinstance(query, str):
        raise ValidationError("Query must be a string")
    
    if len(query) > max_length:
        raise ValidationError(f"Query too long (max {max_length} chars)")
    
    # Remove potentially dangerous characters
    sanitized = re.sub(r'[<>"\']', '', query)
    
    # Additional validation logic
    return sanitized.strip()

# Secure API key handling
def generate_api_key() -> str:
    """Generate cryptographically secure API key"""
    import secrets
    return f"fact_{secrets.token_urlsafe(32)}"

# No hardcoded secrets
def get_api_credentials():
    """Get API credentials from environment"""
    api_key = os.environ.get('ANTHROPIC_API_KEY')
    if not api_key:
        raise ConfigurationError("ANTHROPIC_API_KEY not set")
    return api_key

Dependency Security

# Python security checks
pip-audit                             # Check for known vulnerabilities
bandit -r fact/                      # Security linting
safety check                         # Check dependencies

# Rust security checks  
cargo audit                          # Check for vulnerabilities
cargo deny check                     # Dependency verification

# Node.js security checks
npm audit                            # Check for vulnerabilities
npm audit fix                        # Fix automatically
npx audit-ci                        # CI security checks

🚀 Release Process

Version Management

Follow semantic versioning (SemVer):

MAJOR.MINOR.PATCH

MAJOR: Breaking changes
MINOR: New features (backward compatible)
PATCH: Bug fixes (backward compatible)

Release Checklist

  • All tests pass
  • Documentation updated
  • Changelog updated
  • Version bumped
  • Security audit passed
  • Performance benchmarks run
  • Breaking changes documented
  • Migration guide created (if needed)

Automated Releases

# .github/workflows/release.yml
name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.8'
      
      - name: Install dependencies
        run: |
          pip install build twine
      
      - name: Build Python package
        run: python -m build
      
      - name: Publish to PyPI
        env:
          TWINE_USERNAME: __token__
          TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
        run: twine upload dist/*
      
      - name: Set up Rust
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
      
      - name: Publish Rust crate
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
        run: cargo publish
      
      - name: Set up Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '16'
          registry-url: 'https://registry.npmjs.org'
      
      - name: Publish NPM package
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
        run: npm publish

💬 Community

Communication Channels

  • GitHub Issues: Bug reports and feature requests
  • GitHub Discussions: General questions and community chat
  • Discord: Real-time community support (link in README)
  • Email: [email protected] for security issues

Code of Conduct

Please read and follow our Code of Conduct. We are committed to providing a welcoming and inclusive environment for all contributors.

Recognition

Contributors are recognized in:

  • CONTRIBUTORS.md file
  • Release notes
  • Documentation credits
  • Annual contributor highlights

Thank you for contributing to FACT! Your contributions help make AI-powered context processing accessible and powerful for everyone.

Clone this wiki locally