Thank you for your interest in contributing to ADRScope! This document provides guidelines and workflows for contributing.
Be respectful, inclusive, and collaborative. We're all here to build something useful together.
- Rust 1.85+ (2024 edition)
- cargo-deny -
cargo install cargo-deny - Git with Conventional Commits knowledge
git clone https://github.com/zircote/adrscope.git
cd adrscope
make checkThis runs the quick check pipeline: format, lint, and test.
git checkout -b feature/your-feature-name
# or
git checkout -b fix/bug-descriptionBranch Naming:
feature/- New featuresfix/- Bug fixesdocs/- Documentation changesrefactor/- Code refactoringtest/- Test improvements
Follow the code conventions below.
Before committing, run:
make checkThis executes:
cargo fmt --check- Code formattingcargo clippy -- -D warnings- Lintingcargo test- All tests
For a full CI-style check:
make ciThis adds:
cargo doc- Documentation generationcargo deny check- Supply chain security- MSRV verification
Use Conventional Commits:
git commit -m "feat: add faceted search for technologies"
git commit -m "fix: handle empty ADR frontmatter gracefully"
git commit -m "docs: update library API examples"Commit Types:
| Type | Description |
|---|---|
feat |
New feature |
fix |
Bug fix |
docs |
Documentation only |
style |
Code style (formatting, missing semicolons, etc.) |
refactor |
Code refactoring without behavior change |
perf |
Performance improvement |
test |
Adding or updating tests |
chore |
Build process, tooling, dependencies |
git push origin feature/your-feature-nameOpen a pull request on GitHub with:
- Clear title following conventional commits format
- Description explaining what and why
- Testing notes showing how you verified the changes
- Breaking changes if applicable
ADRScope follows Clean Architecture with four layers:
src/
├── cli/ # Command-line interface
├── application/ # Use cases (orchestration)
├── domain/ # Core business logic (pure, no I/O)
└── infrastructure/ # External concerns (fs, parsing, rendering)
Dependency Rules:
cli→application→domainapplication→infrastructure(for I/O)domainNEVER depends oninfrastructure
- No panics - Use
Resulttypes - No
unwrap()in library code (tests are OK) - Use
thiserrorfor custom errors
// Good
pub fn read_config(path: &Path) -> Result<String> {
std::fs::read_to_string(path)
.map_err(|source| Error::FileRead {
path: path.to_path_buf(),
source,
})
}
// Bad
pub fn read_config(path: &Path) -> String {
std::fs::read_to_string(path).unwrap() // Never in library code
}#![forbid(unsafe_code)]- No unsafe code allowed- Use safe Rust patterns exclusively
Document public APIs with examples:
/// Generates an HTML viewer from ADRs.
///
/// # Arguments
///
/// * `options` - Configuration for generation
///
/// # Returns
///
/// A result containing the number of ADRs processed.
///
/// # Errors
///
/// Returns an error if:
/// - No ADR files are found
/// - File reading fails
/// - HTML rendering fails
///
/// # Examples
///
/// ````no_run
/// use adrscope::application::{GenerateOptions, GenerateUseCase};
/// use adrscope::infrastructure::fs::RealFileSystem;
///
/// let fs = RealFileSystem::new();
/// let use_case = GenerateUseCase::new(fs);
/// let options = GenerateOptions::new("docs/decisions");
///
/// let result = use_case.execute(&options)?;
/// # Ok::<(), adrscope::Error>(())
/// ````
pub fn execute(&self, options: &GenerateOptions) -> Result<GenerateResult> {
// implementation
}Write comprehensive tests:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_valid_frontmatter() {
let input = "---\ntitle: Test\nstatus: accepted\n---\n\n## Context\n\nTest";
let result = parse(input);
assert!(result.is_ok());
assert_eq!(result.unwrap().frontmatter.title, "Test");
}
#[test]
fn test_parse_missing_title_returns_error() {
let input = "---\nstatus: accepted\n---\n\n## Context\n\nTest";
let result = parse(input);
assert!(matches!(result, Err(Error::Parse(_))));
}
}Use InMemoryFileSystem for testing (available under #[cfg(test)] or with feature = "testing"):
use adrscope::infrastructure::fs::test_support::InMemoryFileSystem;
use std::path::Path;
#[test]
fn test_generate_use_case() {
let fs = InMemoryFileSystem::new();
fs.add_file("docs/decisions/adr-001.md", "---\ntitle: Test\nstatus: accepted\n---\n\n## Context\n\nTest");
let use_case = GenerateUseCase::new(fs);
let options = GenerateOptions::new("docs/decisions");
let result = use_case.execute(&options).unwrap();
assert_eq!(result.adr_count, 1);
}We use clippy with pedantic and nursery lints:
cargo clippy -- -D warningsSome lints are allowed for practical reasons (see src/lib.rs for the full list).
Use rustfmt with our configuration:
cargo fmtConfiguration is in rustfmt.toml.
make testOr with cargo directly:
cargo test --verbosecargo test test_namecargo test -- --nocaptureIntegration tests are in tests/integration_test.rs. They test the full pipeline with real file fixtures.
make docOr:
cargo doc --openUser-facing documentation is in docs/:
getting-started.md- Tutorialuser-guide.md- Command referenceconfiguration.md- Configuration optionslibrary-api.md- Library usage guide
When adding features, update relevant documentation.
Architectural decisions are documented in docs/decisions/. When making significant architectural changes, create a new ADR:
cp docs/decisions/adr-0001-template.md docs/decisions/adr-NNNN-your-decision.mdFollow the structured-MADR format.
- Code follows clean architecture principles
- All tests pass (
make test) - Linting passes (
make lint) - Code is formatted (
make fmt) - Documentation updated if needed
- ADR created for architectural changes
- Conventional commit messages used
- PR description explains what and why
- Automated checks - CI runs on all PRs
- Code review - Maintainer reviews for quality and design
- Feedback - Address review comments
- Approval - Maintainer approves changes
- Merge - Squash and merge to main
GitHub Actions runs:
- ✅ Format check (
cargo fmt --check) - ✅ Clippy (
cargo clippy -- -D warnings) - ✅ Tests (
cargo test) - ✅ Documentation (
cargo doc) - ✅ Supply chain security (
cargo deny check) - ✅ MSRV verification (Rust 1.85)
All checks must pass before merging.
Releases are handled by maintainers:
- Update
CHANGELOG.md - Bump version in
Cargo.toml - Create git tag:
git tag -a v0.x.0 -m "Release v0.x.0" - Push tag:
git push origin v0.x.0 - GitHub Actions builds and publishes to crates.io
Quick reference:
make build # Build debug binary
make release # Build optimized binary
make test # Run all tests
make lint # Run clippy
make fmt # Format code
make check # Quick check (fmt + lint + test)
make ci # Full CI pipeline
make doc # Generate documentation
make install # Install to ~/.cargo/bin
make clean # Clean build artifacts- Issues - GitHub Issues
- Discussions - GitHub Discussions
- Chat - Check README for community links
Contributors are recognized in:
- Git commit history
- Release notes in
CHANGELOG.md - GitHub contributors graph
Thank you for contributing! 🎉