-
Notifications
You must be signed in to change notification settings - Fork 35
Contributing
Welcome to FACT! This guide will help you contribute to the Fast Augmented Context Tools project across Python, Rust, WASM, and NPM implementations.
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# 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 testsUnderstanding 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
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
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# 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// 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!()
}/**
* 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 };# 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// 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);
}
}
}// 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();
});
});# 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 lintingSet 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]# 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 updatesFollow 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- Create Feature Branch
git checkout -b feature/add-new-template-type- Make Changes
- Write code following style guidelines
- Add comprehensive tests
- Update documentation
- Ensure all tests pass
- 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- Code Review Process
- At least one reviewer approval required
- All CI checks must pass
- Address review feedback
- Squash commits before merge if requested
# 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);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
"""
passUse 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# 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# 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 checksFollow semantic versioning (SemVer):
MAJOR.MINOR.PATCH
MAJOR: Breaking changes
MINOR: New features (backward compatible)
PATCH: Bug fixes (backward compatible)
- All tests pass
- Documentation updated
- Changelog updated
- Version bumped
- Security audit passed
- Performance benchmarks run
- Breaking changes documented
- Migration guide created (if needed)
# .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- 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
Please read and follow our Code of Conduct. We are committed to providing a welcoming and inclusive environment for all contributors.
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.