diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b47ce02..f562a9d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: stable @@ -21,6 +23,10 @@ jobs: run: cargo audit - name: Deny run: cargo deny check + - name: Gitleaks + uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Test run: cargo test - name: Build release diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..9d5152f --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,23 @@ +repos: + - repo: local + hooks: + - id: fmt + name: cargo fmt + description: Check Rust formatting + entry: cargo fmt --check + language: system + types: [rust] + pass_filenames: false + - id: clippy + name: cargo clippy + description: Lint Rust code with clippy + entry: cargo clippy --all-targets -- -D warnings + language: system + types: [rust] + pass_filenames: false + - id: gitleaks + name: gitleaks + description: Detect secrets in staged changes + entry: /home/linuxbrew/.linuxbrew/bin/gitleaks protect --staged -v + language: system + pass_filenames: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..68366ac --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,40 @@ +# Contributing + +## Commit format + +``` +type: short description +``` + +Types: `feat`, `fix`, `chore`, `docs`, `ci`, `refactor`, `test`. + +Examples: +``` +feat: add switch_repo MCP tool +fix: handle empty query in search +chore: bump rusqlite to 0.41.0 +``` + +## PR process + +1. Branch from master: `git checkout -b feature/my-change` +2. Make changes, run `pre-commit run --all-files` before committing +3. Push and open a PR against master +4. CI must pass (fmt, clippy, audit, deny, test, integration) +5. One review approval required + +## Pre-commit hooks + +```bash +pip install pre-commit # or brew install pre-commit +pre-commit install +``` + +This runs `cargo fmt`, `cargo clippy`, and `gitleaks` on every commit. + +## Security + +- `cargo audit` checks dependencies for vulnerabilities (in CI) +- `gitleaks` checks for secrets (pre-commit + CI) +- `cargo deny` checks license compliance and duplicate deps (in CI) +- Never commit API keys, tokens, or credentials diff --git a/src/main.rs b/src/main.rs index aafdc65..b9b430a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -88,10 +88,7 @@ fn main() { println!("{:.4} {}", score, fp); } } - Commands::Serve { - repo, - cache_dir, - } => { + Commands::Serve { repo, cache_dir } => { let repo_path = Path::new(&repo); let canonical = repo_path .canonicalize() @@ -119,10 +116,7 @@ fn main() { ); mcp_server(canonical.to_str().unwrap_or(&repo).to_string(), &out_dir); } - Commands::Watch { - repo, - cache_dir, - } => { + Commands::Watch { repo, cache_dir } => { let repo_path = Path::new(&repo); let canonical = repo_path .canonicalize() @@ -174,13 +168,15 @@ fn mcp_server(initial_repo: String, initial_out: &std::path::Path) { }); let db_path_of = |cache: &std::path::Path| -> String { - cache.join("phrases.sqlite") - .to_str().unwrap_or("").to_string() + cache + .join("phrases.sqlite") + .to_str() + .unwrap_or("") + .to_string() }; let bodies_path = |cache: &std::path::Path| -> String { - cache.join("bodies.db") - .to_str().unwrap_or("").to_string() + cache.join("bodies.db").to_str().unwrap_or("").to_string() }; let tools = json!([ @@ -232,7 +228,6 @@ fn mcp_server(initial_repo: String, initial_out: &std::path::Path) { } "tools/call" => { let st = state.lock().unwrap(); - let repo = st.repo.clone(); let cache = st.cache_dir.clone(); let db_path = db_path_of(&cache); drop(st);