Thank you for your interest in contributing to Lash! This document provides guidelines and instructions for contributing to the project.
- Code of Conduct
- Getting Started
- How to Contribute
- Development Workflow
- Coding Standards
- Testing Requirements
- Commit Guidelines
- Pull Request Process
- Review Process
We are committed to providing a welcoming and inclusive environment for all contributors. We pledge to:
- Be respectful and considerate in all interactions
- Welcome diverse perspectives and experiences
- Accept constructive criticism gracefully
- Focus on what is best for the community and project
- Show empathy towards other community members
Examples of behavior that contributes to a positive environment:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior:
- The use of sexualized language or imagery
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information without explicit permission
- Other conduct which could reasonably be considered inappropriate
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting the project maintainers. All complaints will be reviewed and investigated promptly and fairly.
Before you begin, ensure you have:
- Rust 1.75 or later (see
rust-toolchain.toml) - Git for version control
- A GitHub account for submitting pull requests
-
Fork the repository on GitHub
-
Clone your fork:
git clone https://github.com/YOUR_USERNAME/lash.git cd lash -
Add upstream remote:
git remote add upstream https://github.com/fixture-dev/lash.git
-
Install pre-commit hooks:
./scripts/install-pre-commit-hook.sh
-
Verify setup:
cargo build --workspace cargo test --workspace
Install recommended tools:
# Coverage reporting
cargo install cargo-llvm-cov
# Watch mode for auto-rebuild
cargo install cargo-watch
# Benchmarking (included with workspace)
cargo bench --workspaceBefore submitting a bug report:
- Check the issue tracker for existing reports
- Verify the bug exists in the latest version
- Collect relevant information (error messages, environment details, steps to reproduce)
When submitting a bug report, include:
- Clear title: Brief description of the issue
- Environment: OS, Rust version, Lash version
- Steps to reproduce: Minimal, reproducible example
- Expected behavior: What you expected to happen
- Actual behavior: What actually happened
- Error messages: Full error output (use code blocks)
- Additional context: Screenshots, logs, related issues
Example bug report:
**Title**: `lash list` crashes when filtering by non-existent label
**Environment**:
- OS: macOS 14.1
- Rust: 1.75.0
- Lash: 0.2.0
**Steps to reproduce**:
1. Initialize a Lash project: `lash init`
2. Run: `lash list --label nonexistent`
**Expected behavior**:
Should return empty results or a helpful message
**Actual behavior**:
Crashes with panic:
\`\`\`
thread 'main' panicked at 'called `Option::unwrap()` on a `None` value'
\`\`\`
**Additional context**:
This only happens when no tasks have the specified labelBefore suggesting a feature:
- Check the design document to see if it's already planned
- Search existing issues for similar suggestions
- Consider if it aligns with Lash's core principles (minimalist, fast, agent-friendly)
When suggesting a feature, include:
- Use case: What problem does this solve?
- Proposed solution: How would it work?
- Alternatives considered: Other approaches you've thought about
- Additional context: Examples, mockups, prior art
Example feature request:
**Title**: Add support for task priorities
**Use case**:
As a user, I want to prioritize tasks so I can focus on the most important work first.
**Proposed solution**:
Add a `@priority` annotation with values: `low`, `medium`, `high`, `critical`
\`\`\`markdown
@priority: high
- [ ] Fix critical bug
\`\`\`
Add CLI filter: `lash list --priority high`
**Alternatives considered**:
- Using labels like `#high-priority` (less structured)
- Custom fields (more complex)
**Additional context**:
Similar to how GitHub issues handle prioritiesFor questions:
- Check the README and developer guide first
- Search GitHub Discussions
- If unanswered, start a new discussion (not an issue)
Always work on a feature branch:
# Update your fork
git fetch upstream
git checkout main
git merge upstream/main
# Create feature branch
git checkout -b feature/my-featureBranch naming conventions:
feature/description- New featuresfix/description- Bug fixesdocs/description- Documentation updatesrefactor/description- Code refactoringtest/description- Test additions
Follow the coding standards:
# Make your changes
vim crates/lash-core/src/parser.rs
# Format code
cargo fmt --all
# Check for issues
cargo clippy --workspace -- -D warnings
# Run tests
cargo test --workspaceEnsure comprehensive testing:
# Unit tests
cargo test --workspace --lib
# Integration tests
cargo test --workspace --test '*'
# Doc tests
cargo test --doc
# All tests
cargo test --workspace --all-targets
# Check coverage (should maintain >80%)
cargo llvm-cov --workspaceUse conventional commits:
git add .
git commit -m "feat: add contextual notes to task parser"git push origin feature/my-featureSee Pull Request Process below.
Formatting: Use rustfmt with default configuration
cargo fmt --allLinting: Zero clippy warnings allowed
cargo clippy --workspace --all-targets -- -D warnings-
DRY Principle: Don't Repeat Yourself
- Extract common logic into functions
- Use generics for reusable patterns
- Avoid copy-paste code
-
Single Responsibility:
- Functions should do one thing well
- Modules should have a clear, focused purpose
- Refactor when files exceed 500 lines
-
Interface-Based Design:
- Program to traits, not concrete types
- Keep public APIs minimal
- Make implementations swappable
-
Error Handling:
- Use
Result<T, LashError>for fallible operations - Provide context with error messages
- Never
unwrap()orexpect()in production code - Use appropriate error codes from the error taxonomy
- Use
-
Performance:
- Don't optimize prematurely
- Benchmark performance-critical code
- Document Big-O complexity for algorithms
- Use
#[inline]judiciously
All public APIs must have:
/// Brief one-line description
///
/// More detailed explanation of what this function does,
/// its behavior, and important details.
///
/// # Arguments
///
/// * `param1` - Description of parameter
/// * `param2` - Description of parameter
///
/// # Returns
///
/// Description of return value
///
/// # Errors
///
/// * `E_ERROR_CODE` - When this error occurs
///
/// # Examples
///
/// ```
/// use lash_core::parser::parse_file;
/// use lash_types::LashConfig;
///
/// let config = LashConfig::default();
/// let result = parse_file(path, &config)?;
/// # Ok::<(), lash_types::LashError>(())
/// ```
pub fn parse_file(path: &Path, config: &LashConfig) -> Result<TaskFile> {
// Implementation
}Doctests:
- All public APIs should have executable doctests
- Use
#prefix to hide boilerplate setup - Prefer runnable examples (avoid
ignore) - Use
no_runfor examples requiring I/O
-
Functions/methods:
snake_case- Constructors:
new(),with_*(),from_*() - Conversions:
to_*()(consumes),as_*()(borrows),into_*()(consumes) - Fallible:
try_*(),*_checked()
- Constructors:
-
Types:
PascalCase- Structs:
TaskFile,DependencyGraph - Enums:
TaskStatus,LashError - Traits:
Parseable,Indexable
- Structs:
-
Constants:
SCREAMING_SNAKE_CASEDEFAULT_MAX_DEPTH,CONFIG_FILE_NAME
-
Modules:
snake_caseparser,linter,graph_builder
Maintain these coverage targets:
- Overall: >80% line coverage
- Critical modules (parser, linter, dependency resolution): >90%
- Less critical (TUI, agent utilities): >70%
Check coverage:
cargo llvm-cov --workspace --html
open target/llvm-cov/html/index.htmlUnit Tests: Fast, isolated tests for single functions
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_checkbox_open() {
let input = "- [ ] Task";
let result = parse_checkbox(input).unwrap();
assert_eq!(result.status, TaskStatus::Open);
}
}Integration Tests: Multi-component tests
// crates/lash-db/tests/indexing_tests.rs
#[test]
fn test_incremental_indexing() {
let tmp = TempDir::new().unwrap();
// Test setup...
let report = indexer.index_project().unwrap();
assert_eq!(report.files_indexed, 5);
}End-to-End Tests: Full CLI workflow tests
use assert_cmd::Command;
#[test]
fn test_lint_command() {
Command::cargo_bin("lash")
.unwrap()
.arg("lint")
.arg("file.md")
.assert()
.success();
}Doc Tests: API examples as tests
/// ```
/// use lash_core::parser::parse_file;
/// let result = parse_file(path, &config)?;
/// # Ok::<(), lash_types::LashError>(())
/// ```
- Test one thing: Each test should verify one specific behavior
- Descriptive names:
test_parser_handles_empty_checkbox_list - Arrange-Act-Assert: Structure tests clearly
- Test error cases: Don't just test happy paths
- Avoid flakiness: Tests must be deterministic
- Keep tests fast: Unit tests should run in milliseconds
Install the pre-commit hook:
./scripts/install-pre-commit-hook.shThe hook runs before each commit:
cargo fmt --check- Code formattingcargo clippy --workspace -- -D warnings- Lint checkscargo test --workspace --lib- Unit testscargo test --doc- Doc tests
To bypass (not recommended):
git commit --no-verifyUse the Conventional Commits format:
<type>(<scope>): <description>
[optional body]
[optional footer]
Types:
feat- New featurefix- Bug fixdocs- Documentation changesstyle- Code style changes (formatting, no logic changes)refactor- Code refactoringperf- Performance improvementstest- Adding or updating testschore- Maintenance tasks (dependencies, build config)ci- CI/CD changes
Scope (optional): Module or component affected
parser,linter,db,cli,tui,agent
Examples:
feat(parser): add support for contextual notes
fix(db): resolve indexing crash on empty files
docs: update developer guide with testing section
refactor(cli): extract command handlers to separate module
test(linter): add test cases for depth validation
perf(indexer): optimize file hash computationDescription (first line):
- Use imperative mood ("add" not "added")
- Keep under 72 characters
- Don't end with a period
- Be specific and clear
Body (optional):
- Explain what and why (not how)
- Wrap at 72 characters
- Separate from description with blank line
Footer (optional):
- Reference issues:
Fixes #123,Closes #456 - Note breaking changes:
BREAKING CHANGE: description
Example with body:
feat(agent): add token counting for prompt generation
This adds a TokenCounter utility that estimates the number of tokens
in a given prompt using a simplified GPT-style tokenization model.
This helps agents stay within token budget constraints.
Closes #234
Checklist:
- Code follows style guidelines (
cargo fmt,cargo clippy) - Tests pass (
cargo test --workspace) - New tests added for new functionality
- Doctests added for new public APIs
- Documentation updated (if applicable)
- Error codes documented (if new errors)
- Benchmarks run (if performance-critical)
- No clippy warnings
- Commit messages follow conventional format
-
Push to your fork:
git push origin feature/my-feature
-
Open PR on GitHub: Go to https://github.com/fixture-dev/lash/pulls
-
Fill out PR template:
## Description Brief summary of changes ## Motivation Why is this change needed? ## Changes - Added X - Modified Y - Fixed Z ## Testing - [ ] Unit tests added - [ ] Integration tests added - [ ] Manual testing performed ## Checklist - [ ] Code formatted with `cargo fmt` - [ ] No clippy warnings - [ ] Tests pass - [ ] Documentation updated ## Related Issues Fixes #123
Use conventional commit format:
feat: add contextual notes supportfix: resolve parser crash on malformed headingsdocs: improve developer guide
Keep PRs focused and manageable:
- Small PRs (<200 lines): Easier to review, faster to merge
- Medium PRs (200-500 lines): Acceptable if well-organized
- Large PRs (>500 lines): Consider splitting into smaller PRs
Tips for large changes:
- Break into logical, incremental PRs
- Submit infrastructure/setup PRs first
- Add features in separate, focused PRs
- Correctness: Does it work? Are edge cases handled?
- Code Quality: Follows style guide, no anti-patterns
- Tests: Comprehensive coverage, tests the right things
- Documentation: Public APIs documented, clear explanations
- Performance: No obvious performance issues or regressions
- Maintainability: Clear, readable code with good abstractions
- Initial review: Within 48 hours (typically)
- Follow-up reviews: Within 24 hours of updates
- Merge: After approval and CI passes
Best practices:
- Be receptive to feedback
- Ask questions if something is unclear
- Make requested changes in new commits (don't force-push during review)
- Respond to all comments (even if just "Done")
- Discuss disagreements respectfully
Example responses:
> Consider extracting this into a helper function
Good idea! I've extracted it into `parse_annotation_value()` in commit abc123.
> This test seems to duplicate test_parse_checkbox_open
You're right. I've removed this test and enhanced the existing one instead.
> Why not use the existing `normalize_path()` function?
I tried that initially, but it doesn't handle relative paths correctly for
this use case. I can add a comment explaining the difference if that helps?Once approved and CI passes:
- Squash commits (if requested by maintainer)
- Update branch with latest
main(if needed) - Wait for merge (maintainers will merge)
- Make the changes locally
- Add tests for the fixes
- Commit with clear message
- Push to update the PR
- Reply to review comments
# Make changes
vim src/parser.rs
# Test
cargo test
# Commit
git add .
git commit -m "refactor: extract helper function per review feedback"
# Push
git push origin feature/my-featureThis project uses Lash itself for task tracking (meta!):
- Check
tasks/tasks.mdfor current development status - Update task status when completing work
- Add new tasks for discovered work
- Link PRs to task IDs where applicable
When optimizing:
- Measure first: Use
cargo benchto establish baseline - Profile: Identify actual bottlenecks
- Optimize: Make targeted improvements
- Verify: Re-benchmark to confirm improvement
- Document: Add comments explaining optimizations
Benchmark example:
# Establish baseline
cargo bench --bench parser_bench -- --save-baseline before
# Make changes...
# Compare
cargo bench --bench parser_bench -- --baseline beforeReleases are automated with cargo-dist
via .github/workflows/release.yml. Pushing a version tag builds binaries for
Linux, macOS, and Windows, generates installer scripts, and publishes a GitHub
Release with notes taken from the matching CHANGELOG.md section.
To cut a release:
-
On a branch, bump
versionin the rootCargo.toml([workspace.package]) -
Move entries from
[Unreleased]to a new## [X.Y.Z] - YYYY-MM-DDsection inCHANGELOG.mdand update the link references at the bottom -
Open a pull request and merge it once required checks pass (
mainonly accepts changes via PR) -
Tag the merge commit and push the tag:
git checkout main && git pull git tag vX.Y.Z git push origin vX.Y.Z
After changing dist settings in dist-workspace.toml, run dist generate to
regenerate the workflow and commit the result β do not edit release.yml by hand.
The plan job runs dist plan, which fails the build if release.yml has
drifted from dist-workspace.toml, so a hand-edit is caught on the next PR.
Each release also publishes a formula to the
fixture-dev/homebrew-tap repo,
which backs brew install fixture-dev/tap/lash. The publish job commits
Formula/lash.rb to that repo using a HOMEBREW_TAP_TOKEN secret β a personal
access token with contents: write on the tap repo only.
The formula is generated, not maintained by hand: it points at the prebuilt
release tarballs for both macOS architectures and both Linux architectures, so
brew install downloads a binary rather than compiling. Nothing needs doing per
release.
If a release succeeds but the formula does not update, HOMEBREW_TAP_TOKEN has
most likely expired β reissue it and re-run the failed job. The publish job is
skipped for prereleases unless publish-prereleases is enabled.
We own the publish job. dist's built-in publish-homebrew-formula is broken
in 0.28.5+: it checks out the tap with persist-credentials: false and then ends
in a bare git push that needs those credentials, failing with
could not read Username for 'https://github.com/'
(astral-sh/cargo-dist#29,
still open as of 0.28.7 and unfixed on upstream main).
So dist-workspace.toml sets publish-jobs = ["./homebrew-tap"], and dist
generates a caller job that invokes our own
.github/workflows/homebrew-tap.yml with
the release plan and secrets: inherit. release.yml stays fully generated, so
dist's drift check stays enabled.
The alternative β hand-patching the generated file β requires
allow-dirty = ["ci"], which disables that drift check for the whole release
workflow and lets future config changes silently fail to reach release.yml.
That is why it was rejected.
Our version also skips brew style --fix (the formula is generated, so the only
effect is paying for a brew update each release) and is safe to re-run: an
unchanged formula is a no-op instead of a "nothing to commit" failure.
If #29 is ever fixed, delete homebrew-tap.yml and set
publish-jobs = ["homebrew"].
If you discover a security vulnerability:
- Do NOT open a public issue
- Email maintainers directly (see README for contact)
- Include full details and steps to reproduce
- Wait for response before public disclosure
Security-focused changes:
- Add tests demonstrating the vulnerability is fixed
- Include CVE numbers if applicable
- Document in
CHANGELOG.mdunder "Security"
By contributing, you agree that your contributions will be licensed under the same terms as the project (Apache License, Version 2.0).
If you need help:
- Documentation: Check README, developer guide, design doc
- Discussions: Browse or start a GitHub Discussion
- Issues: Search existing issues for similar problems
- Ask: If stuck, open a discussion or comment on a related issue
Be specific when asking:
- What you're trying to accomplish
- What you've tried so far
- Error messages or unexpected behavior
- Environment details (OS, Rust version)
Contributors will be recognized in:
CHANGELOG.mdfor each release- GitHub contributors page
- Mentioned in release notes (for significant contributions)
Thank you for contributing to Lash!
Note: the mutation-testing check does not run on fork PRs (repository secrets are unavailable to them); a maintainer pushing the branch to the main repo exercises it.