# DiffSift

**Classify code changes by semantic impact.** DiffSift uses Tree-sitter static analysis to identify which hunks in a diff are logic bugs, security risks, API breaks, or safe refactoring — so you (or an LLM reviewer) focus on what matters.

```
$ diffsift classify --repo . --base main --head HEAD

Priority  | Hunks
----------|------
HIGH      | 3
MODERATE  | 5
LOW       | 12
FILTERED  | 8

HIGH findings:
  src/auth.ts:42-44 — Logic (comparison-operator-change) in checkPermission [L-01]
  src/db.ts:18-20   — DataFlow (sql-interpolation-in-query) in runQuery [D-02]
  src/api.ts:55-58  — ApiSurface (export-removed) in fetchUser [A-02]
```

## What it does

1. **Computes the diff** between two git refs (or from a raw patch)
2. **Parses changed files** with Tree-sitter (15 languages)
3. **Maps diff lines to AST nodes** (functions, classes, expressions)
4. **Classifies each hunk** using 231 deterministic rules across 14 categories
5. **Optionally calls your LLM** to review only the high-priority hunks
6. **Posts prioritized comments** to your PR (GitHub, GitLab)

No hosted server. No third-party infrastructure. Runs in your CI pipeline.

## Install

```bash
# Cargo (recommended)
cargo install diffsift

# Homebrew
brew tap diffsift/diffsift
brew install diffsift

# curl installer (Linux/macOS)
curl -fsSL https://gitlab.com/neilfitzgerald1972/diffsift/-/raw/main/install.sh | sh
```

## Quick start

```bash
# Initialize config
diffsift init

# Classify a diff (no API key needed)
diffsift classify --repo . --base main --head HEAD

# Full AI-assisted review (needs ANTHROPIC_API_KEY or OPENAI_API_KEY)
diffsift review --repo . --base main --head HEAD

# Preview what would be reviewed
diffsift review --repo . --base main --head HEAD --dry-run
```

### GitLab CI

```yaml
diffsift:
  image: rust:latest
  script:
    - curl -fsSL https://gitlab.com/neilfitzgerald1972/diffsift/-/raw/main/install.sh | sh
    - diffsift review --repo . --base $CI_MERGE_REQUEST_DIFF_BASE_SHA --head HEAD
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
```

## Classification

DiffSift classifies hunks into **14 categories** at **4 priority levels**:

| Priority | What it means | Action |
|---|---|---|
| **HIGH** | Logic bugs, security vulnerabilities, API breaks | Always review |
| **MODERATE** | Data flow changes, control flow modifications | Review if time permits |
| **LOW** | Structural refactoring, renames | Safe to skip |
| **FILTERED** | Formatting, whitespace | Ignore |

### Categories

| Category | Rules | What it detects |
|---|---|---|
| Logic | 40 | Comparison operators, boolean logic, numeric literals, off-by-one, nullable regressions |
| DataFlow | 33 | Type changes, null propagation, async/await, taint flows, EF queries, IDisposable |
| Security | 25 | Injection, auth bypass, crypto downgrade, CSRF, .NET auth attributes |
| ControlFlow | 21 | Error handling changes, guard removal, async→sync |
| ApiSurface | 18 | Breaking API changes, removed exports, visibility changes |
| Config | 19 | CI/Docker/K8s/Terraform changes, dangerous defaults |
| Structural | 13 | Renames, extract function, reorder methods |
| Concurrency | 15 | Race conditions, lock/volatile removal, ConcurrentDictionary downgrades |
| Dependency | 10 | Version bumps, removed packages, manifest changes |
| Test | 11 | Deleted tests, weakened assertions, skipped tests |
| TypeSafety | 4 | `as any` casts, `@ts-ignore`, `# type: ignore` |
| AiPatterns | 3 | Hallucinated imports, incomplete stubs, hardcoded literals |
| Taint | 3 | SQL injection, XSS, command injection via data flow |
| Formatting | 3 | Whitespace, template literal conversion, comments |

231 rules total (218 per-hunk + 13 cross-hunk), 33 CWE mappings. See [PATTERNS.md](PATTERNS.md) for the full catalog.

## Languages

TypeScript, JavaScript, Python, Go, Java, C#, Rust, Elixir, PHP, Ruby, Kotlin, Swift, Bash, PowerShell, XML.

## Configuration

DiffSift reads `.diffsift/config.toml` from your repo root:

```toml
[llm]
provider = "anthropic"                    # or "openai"
model_high = "claude-sonnet-4-20250514"   # for HIGH priority hunks
model_moderate = "claude-haiku-4-5-20251001"

[review]
review_priorities = ["high", "moderate"]
max_comments = 15
max_cost_cents = 50
small_diff_threshold = 8                  # below this, use simplified prompt
churn_window_days = 14                    # cross-PR churn detection lookback (0 = disabled)
churn_threshold = 4                       # file changes in window to flag as churning

[prompts]
system_preamble = "Follow our team's security policy."
review_addendum = "Also check for timezone handling bugs."

[prompts.category_guidance]
security = "Focus on OWASP Top 10 and our internal AppSec checklist."
```

### Custom rules

Path-based priority overrides in `.diffsift/rules.toml`:

```toml
[[rules]]
paths = ["src/payments/**", "src/billing/**"]
min_priority = "high"

[[rules]]
paths = ["scripts/**", "tools/**"]
max_priority = "low"
```

### Custom templates

Override LLM prompts with Jinja2 templates. See [docs/TEMPLATES.md](docs/TEMPLATES.md).

## Output formats

```bash
diffsift classify --format summary      # human-readable (default)
diffsift classify --format json          # structured JSON
diffsift classify --format summary-json  # compact JSON for LLM consumption
diffsift classify --format sarif         # SARIF 2.1.0 for GitHub Code Scanning
```

## Integrations

| Integration | How |
|---|---|
| **AI agents** | `diffsift mcp` — MCP server with 4 tools (classify_diff, classify_patch, get_high_risk, summarize) |
| **reviewdog** | `diffsift classify --format sarif \| reviewdog -f sarif` |
| **trunk.io** | `.trunk/trunk.yaml` plugin definition included |
| **GitHub** | SARIF output for Code Scanning |
| **GitLab** | CI config + merge request comments |

## As a Rust library

```rust
use diffsift_core::{classify_diff, classify_patch, classify_file_diff};

// Classify a git diff
let result = classify_diff(Path::new("."), "main", "HEAD")?;

// Classify a raw patch (no git repo needed)
let result = classify_patch(&unified_diff)?;

// Compare two file versions
let result = classify_file_diff(path, &old_content, &new_content, None)?;

// Builder for custom configuration
let sift = diffsift_core::DiffSift::builder()
    .disable_category(ChangeCategory::Formatting)
    .min_priority(Priority::Moderate)
    .build();
```

## Commands

| Command | Description |
|---|---|
| `classify` | Classify hunks by category and priority |
| `review` | Classify + LLM review + post PR comments |
| `init` | Create `.diffsift/config.toml` |
| `scan` | Scan git history for patterns (alias: `history`) |
| `baseline` | Snapshot known findings for suppression |
| `check` | Validate config and custom rules |
| `calibrate` | Learn from PR comment reactions (👍/👎) |
| `report` | Review quality report from calibration data |
| `mcp` | MCP server for AI agent integration |

## Development

```bash
cargo build          # compile
cargo test           # 953 tests
cargo clippy         # lint
cargo fmt --check    # format check

# Run the full CI pipeline locally:
./scripts/ci-local.sh
```

A `pre-push` git hook runs the same checks before every push. Enable it once:

```bash
git config core.hooksPath .githooks
```

Bypass with `git push --no-verify` when needed.

## How it works

DiffSift is **BYOLLM** (Bring Your Own LLM). The classification engine is deterministic — it uses Tree-sitter AST parsing and 231 pattern-matching rules to classify hunks without any LLM calls. The LLM is only used (optionally) to generate review comments for high-priority hunks.

This means:
- `diffsift classify` works with **zero configuration** — no API keys, no network
- Classification is **sub-second** per file
- Results are **reproducible** — same diff always gets the same classification
- LLM costs are **minimized** — only high-priority hunks are sent for review

## Status & feedback

DiffSift is in early release. The classification engine is solid — 231 rules, 953 tests, 100% detection on 40 benchmark scenarios — but rules get better with real-world usage. If you try it on your codebase and see false positives, missed bugs, or categories that don't make sense for your domain, **please open an issue**. That feedback directly improves the rules for everyone.

What's most useful right now:
- Running `diffsift classify` on real PRs and reporting what it gets wrong
- Suggesting new rule categories for your domain (fintech, healthcare, infra, etc.)
- Testing on languages beyond TypeScript/Python (Go, Rust, Kotlin, etc. are less battle-tested)

## Built with AI

DiffSift was developed with substantial assistance from Claude (Anthropic). The rule implementations, test suites, taint analysis, and much of the infrastructure were pair-programmed with AI. The architecture, design decisions, and domain knowledge are human-directed; the implementation velocity comes from human-AI collaboration.

This is noted for transparency — and because DiffSift is a tool that uses AI to review code that may itself be AI-generated. We think that's a feature, not a contradiction.

## License

MIT
