Thank you for your interest in contributing to Skim! We welcome contributions from the community.
Be respectful, constructive, and professional. We're here to build great tools together.
Before creating a bug report:
- Check existing issues to avoid duplicates
- Verify you're using the latest version
- Test with minimal example to isolate the issue
Good bug report includes:
- Skim version (
skim --version) - Operating system and architecture
- Minimal code example that reproduces the issue
- Expected vs actual behavior
- Error messages (if any)
Before suggesting a feature:
- Check if it aligns with project scope (see CLAUDE.md for design constraints)
- Search existing issues for similar requests
- Consider if it can be implemented as a separate tool
Good feature request includes:
- Clear use case and motivation
- Example of how it would work
- Consideration of edge cases
- Willingness to contribute implementation
- Use GitHub Discussions for questions
- Use Issues only for bugs and feature requests
- Check existing discussions first
- Rust 1.70+ (
rustup update) - Git
- Optional:
cargo-watchfor auto-rebuild during development
git clone https://github.com/dean0x/skim.git
cd skim
cargo build
cargo testskim/
├── crates/
│ ├── rskim-core/ # Core library (no I/O, pure transforms)
│ │ ├── src/
│ │ │ ├── lib.rs # Public API
│ │ │ ├── types.rs # Core types (Language, Mode, etc.)
│ │ │ ├── parser/ # tree-sitter wrapper
│ │ │ └── transform/ # Transformation logic
│ │ ├── tests/ # Integration tests
│ │ └── benches/ # Benchmarks (criterion)
│ └── rskim/ # CLI binary (I/O layer)
│ └── src/main.rs # Argument parsing, file I/O
├── tests/fixtures/ # Test files for each language
├── .docs/ # Internal documentation
└── CLAUDE.md # AI assistant instructions
Design principles:
rskim-core: No I/O, no CLI dependencies, pure libraryrskim: Thin I/O wrapper, delegates to core- All business logic in core, tested there
# Watch mode (auto-rebuild on changes)
cargo watch -x build -x test
# Run specific test
cargo test test_name
# Run with debug output
RUST_LOG=debug cargo run -- file.ts
# Format code
cargo fmt
# Lint
cargo clippy -- -D warnings
# Check before commit
cargo fmt --check && cargo clippy -- -D warnings && cargo testgit checkout -b feature/my-feature
# or
git checkout -b fix/my-bugfixFollow existing patterns:
- Use
Result<T, SkimError>for error handling (no panics) - Add doc comments for public APIs
- Keep functions focused and testable
- Prefer
&stroverStringfor zero-copy - Update tests alongside code changes
Lint requirements:
// ✅ GOOD: Explicit error handling
let result = parse(source)?;
// ❌ BAD: Will fail clippy
let result = parse(source).unwrap(); // unwrap_used = denyEvery change needs tests:
#[test]
fn test_my_feature() {
let source = "test code";
let result = transform(source, Language::TypeScript, Mode::Structure);
assert!(result.is_ok());
}Test organization:
- Unit tests:
#[cfg(test)] mod testsin same file - Integration tests:
tests/directory - Fixtures:
tests/fixtures/<language>/
- Add/update doc comments for public APIs
- Update README.md if adding user-facing features
- Update CHANGELOG.md under
[Unreleased] - Add examples if introducing new functionality
Follow existing commit style:
git commit -m "Add signature extraction for Python decorators
- Parse decorator nodes in AST traversal
- Preserve @decorator syntax in output
- Add test fixtures for common decorator patterns
Fixes #123"Commit message format:
<Action> <what> (<context>)
- Bullet points explaining changes
- Focus on WHY, not just WHAT
- Reference issue numbers if applicable
git push origin feature/my-featureThen create a Pull Request on GitHub with:
- Clear description of what changed and why
- Link to related issues
- Screenshots/examples if UI/output changed
- Confirmation that tests pass
Adding language support is straightforward:
Check https://github.com/tree-sitter for tree-sitter-<language>
# Cargo.toml
[workspace.dependencies]
tree-sitter-newlang = "0.23" # Must be 0.23.x# crates/rskim-core/Cargo.toml
[dependencies]
tree-sitter-newlang = { workspace = true }// crates/rskim-core/src/types.rs
pub enum Language {
// ... existing
NewLang,
}
impl Language {
pub fn from_extension(ext: &str) -> Option<Self> {
match ext {
// ... existing
"newext" => Some(Self::NewLang),
_ => None,
}
}
pub fn to_tree_sitter(self) -> tree_sitter::Language {
match self {
// ... existing
Self::NewLang => tree_sitter_newlang::LANGUAGE.into(),
}
}
}// crates/rskim-core/src/transform/structure.rs
fn get_node_types_for_language(language: Language) -> NodeTypes {
match language {
// ... existing
Language::NewLang => NodeTypes {
function: "function_definition", // Check grammar docs
method: "method_definition",
},
}
}mkdir -p tests/fixtures/newlangCreate tests/fixtures/newlang/simple.newext:
// Simple example with functions, classes, etc.
#[test]
fn test_parser_newlang() {
let source = "function example() { }";
let mut parser = Parser::new(Language::NewLang).unwrap();
let result = parser.parse(source);
assert!(result.is_ok());
}- Add to language table in README.md
- Update CHANGELOG.md
- Add examples in README if syntax differs significantly
Total time: ~30 minutes for straightforward languages.
Run tests with coverage (requires cargo-tarpaulin):
cargo install cargo-tarpaulin
cargo tarpaulin --out HtmlCoverage goals:
- Core library: >80%
- Transform logic: >90%
- CLI: >60% (I/O makes 100% hard)
- Unit tests: Test individual functions
- Integration tests: Test full transformation pipeline
- Fixture tests: Test real-world code samples
- Snapshot tests: Use
instafor output comparison (planned)
// ✅ GOOD: Clear, focused test
#[test]
fn test_preserves_function_signature() {
let source = "function add(a: number, b: number): number { return a + b; }";
let result = transform(source, Language::TypeScript, Mode::Structure).unwrap();
assert!(result.contains("function add(a: number, b: number): number"));
assert!(result.contains("{ /* ... */ }"));
}
// ❌ BAD: Testing too many things
#[test]
fn test_everything() {
// Tests 10 different features in one test
}Performance is a first-class concern.
cargo bench # Runs criterion benchmarksPerformance targets (verified):
- Parse + transform: 14.6ms for 3000-line files ✅ (target was <50ms for 1000 lines)
- Small files (<100 lines): 33-84µs depending on language
- Memory: <10MB for typical files
- Startup: <10ms
// ✅ GOOD: Zero-copy with &str
let text = node.utf8_text(source.as_bytes())?;
// ❌ BAD: Unnecessary allocation
let text = node.text().to_string();// ✅ GOOD: Pre-allocate with capacity
let mut result = String::with_capacity(source.len());
// ❌ BAD: Let it grow
let mut result = String::new();-
Automated checks must pass:
cargo fmt --checkcargo clippy -- -D warningscargo test- CI pipeline (GitHub Actions)
-
Manual review focuses on:
- Correctness and safety
- Performance implications
- API design
- Test coverage
- Documentation quality
-
Approval: At least one maintainer approval required
-
Merge: Squash and merge (keep history clean)
(For maintainers)
- Update version in
Cargo.tomlfiles - Update
CHANGELOG.md(move Unreleased to new version) - Commit:
git commit -m "Release v0.2.0" - Tag:
git tag v0.2.0 - Push:
git push --tags - CI will automatically publish to crates.io (when configured)
- General questions: GitHub Discussions
- Bug reports: GitHub Issues
- Security issues: See SECURITY.md
Contributors are credited in:
- Git commit history
- Release notes in CHANGELOG.md
- Special thanks in README.md (for significant contributions)
Thank you for contributing to Skim! 🎉