Thank you for your interest in contributing to Scout!
Be respectful, inclusive, and constructive.
-
Clone the repository:
git clone https://github.com/Hyphonical/Scout.git cd Scout -
Install Rust (if not already installed):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
-
Build the project:
cargo build
-
Run tests:
cargo test -
Download models
Scout follows standard Rust conventions:
- Use
cargo fmtbefore committing - Run
cargo clippyand fix warnings - Add documentation comments for public APIs
- Use descriptive variable names
- Keep functions focused and small
src/
cli.rs # Command-line interface (clap)
main.rs # Entry point
config.rs # Constants and configuration
core/ # Domain types
embedding.rs # Embedding vector type
hash.rs # File hashing (xxHash)
media.rs # Media type detection
models/ # ML model management
vision.rs # Vision model (SigLIP2)
text.rs # Text model (SigLIP2)
manager.rs # Lazy loading, model lifecycle
runtime/ # ONNX Runtime integration
providers.rs # Execution provider selection
storage/ # Persistence layer
sidecar.rs # Sidecar format (MessagePack)
index.rs # Sidecar discovery
processing/ # Media processing
scan.rs # Directory scanning
image.rs # Image loading and encoding
video.rs # FFmpeg integration
commands/ # CLI command implementations
scan.rs # scan command
search.rs # search command
clean.rs # clean command
ui/ # User interface
log.rs # Logging and output formatting
- Files:
snake_case.rs - Types:
PascalCase - Functions:
snake_case() - Constants:
SCREAMING_SNAKE_CASE - Modules:
snake_case
-
Create command file:
touch src/commands/my_command.rs
-
Implement command:
//! My command - does something useful use anyhow::Result; use std::path::Path; use crate::ui; pub fn run(dir: &Path) -> Result<()> { ui::info("Running my command"); // ... implementation Ok(()) }
-
Add to CLI (
src/cli.rs):#[derive(Subcommand)] pub enum Command { // ... existing commands /// My new command MyCommand { #[arg(short, long)] dir: PathBuf, }, }
-
Wire up in
src/main.rs:cli::Command::MyCommand { dir } => { commands::my_command::run(&dir) }
-
Export in
src/commands/mod.rs:pub mod my_command;
To add a new search filter:
-
Add CLI argument (
src/cli.rs):Search { // ... existing fields #[arg(long)] my_filter: bool, }
-
Update search function signature (
src/commands/search.rs):pub fn run( // ... existing params my_filter: bool, ) -> Result<()>
-
Implement filter logic:
if my_filter && !meets_condition(&result) { continue; }
-
Update main.rs call:
cli::Command::Search { /* ... */, my_filter } => { commands::search::run(/* ... */, my_filter) }
-
Add to Cargo.toml (platform-specific):
[target.'cfg(target_os = "linux")'.dependencies] ort = { version = "2.0.0-rc.11", features = ["std", "my-provider"] }
-
Add to Provider enum (
src/cli.rs):pub enum Provider { // ... existing MyProvider, }
-
Add to macro (
src/runtime/providers.rs):create_provider_fn! { // ... existing (MyProvider, "MyProvider", my_provider), }
# All tests
cargo test
# Specific test
cargo test test_embedding_similarity
# With output
cargo test -- --nocaptureAdd tests in the same file:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_my_feature() {
let result = my_function();
assert_eq!(result, expected);
}
}Or in a separate tests/ directory for integration tests.
Focus on:
- Core logic (embeddings, similarity)
- Data structures (serialization)
- Edge cases (empty inputs, invalid data)
- Error handling
Use doc comments:
/// Computes similarity between two embeddings.
///
/// Returns a score between 0.0 (completely different) and 1.0 (identical).
///
/// # Example
///
/// ```
/// let e1 = Embedding::new(vec![1.0, 0.0]);
/// let e2 = Embedding::new(vec![0.0, 1.0]);
/// let sim = e1.similarity(&e2);
/// assert!(sim >= 0.0 && sim <= 1.0);
/// ```
pub fn similarity(&self, other: &Embedding) -> f32 {
// ...
}Update docs when adding features:
docs/ARCHITECTURE.md- Technical detailsREADME.md- Quick reference
-
Fork the repository
-
Create a feature branch:
git checkout -b feature/my-feature
-
Make your changes:
- Write clear, focused commits
- Add tests for new features
- Update documentation
-
Verify quality:
cargo fmt cargo clippy cargo test cargo build --release -
Push and create PR:
git push origin feature/my-feature
Then create PR on GitHub with:
- Clear description of changes
- Motivation and context
- Testing done
- Screenshots (if UI changes)
-
Address review feedback
-
Merge! (after approval)
- Keep PRs focused and small
- Write clear commit messages
- Respond to feedback promptly
- Be open to suggestions
- Be constructive and specific
- Focus on correctness, clarity, maintainability
- Suggest improvements, don't demand perfection
- Approve when ready
-
Add to
Cargo.toml:[dependencies] my-crate = "1.0"
-
Use in code:
use my_crate::Thing;
-
Document why it's needed in PR
Models are external artifacts. To update:
- Train/export new ONNX models
- Update model version in docs
- Test thoroughly
- Create release with new models
# Build with profiling symbols
cargo build --release
# Run with profiler
perf record ./target/release/scout scan -d photos/
perf report
# Or use flamegraph
cargo install flamegraph
cargo flamegraph -- scan -d photos/# Verbose logging
scout --verbose scan -d photos/
# Rust backtrace
RUST_BACKTRACE=1 scout scan -d photos/
# Debug build (with symbols)
cargo build
gdb ./target/debug/scout- Update version in
Cargo.toml - Update CHANGELOG.md
- Tag release:
git tag -a v2.1.0 -m "Release v2.1.0" git push origin v2.1.0 - Build binaries for all platforms
- Create GitHub Release with notes and binaries
- Open an issue for bugs or feature requests
- Start a discussion for questions or ideas
Thank you for contributing! 🎉