{"id":13484819,"url":"https://github.com/afnanenayet/diffsitter","last_synced_at":"2026-05-22T08:07:17.993Z","repository":{"id":37779489,"uuid":"275508429","full_name":"afnanenayet/diffsitter","owner":"afnanenayet","description":"A tree-sitter based AST difftool to get meaningful semantic diffs","archived":false,"fork":false,"pushed_at":"2026-05-13T06:12:38.000Z","size":2394,"stargazers_count":2372,"open_issues_count":32,"forks_count":52,"subscribers_count":10,"default_branch":"main","last_synced_at":"2026-05-13T12:49:39.610Z","etag":null,"topics":["ast","diff","parser","rust","tree-sitter"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/afnanenayet.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"docs/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"docs/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-06-28T04:45:22.000Z","updated_at":"2026-05-09T07:22:22.000Z","dependencies_parsed_at":"2026-04-01T23:00:39.550Z","dependency_job_id":null,"html_url":"https://github.com/afnanenayet/diffsitter","commit_stats":{"total_commits":696,"total_committers":15,"mean_commits":46.4,"dds":0.4339080459770115,"last_synced_commit":"16bb9a22b3f5f69744a0aca9c5cf28f33f090aa6"},"previous_names":[],"tags_count":40,"template":false,"template_full_name":null,"purl":"pkg:github/afnanenayet/diffsitter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afnanenayet%2Fdiffsitter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afnanenayet%2Fdiffsitter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afnanenayet%2Fdiffsitter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afnanenayet%2Fdiffsitter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/afnanenayet","download_url":"https://codeload.github.com/afnanenayet/diffsitter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afnanenayet%2Fdiffsitter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33334778,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-21T12:23:38.849Z","status":"online","status_checked_at":"2026-05-22T02:00:06.671Z","response_time":265,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ast","diff","parser","rust","tree-sitter"],"created_at":"2024-07-31T17:01:34.813Z","updated_at":"2026-05-22T08:07:17.956Z","avatar_url":"https://github.com/afnanenayet.png","language":"Rust","funding_links":[],"categories":["Rust","rust","Systems \u0026 Low-Level","Source Code"],"sub_categories":["Rust","Semantic Diffs"],"readme":"# diffsitter\n\n[![CI](https://github.com/afnanenayet/diffsitter/actions/workflows/CI.yml/badge.svg)](https://github.com/afnanenayet/diffsitter/actions/workflows/CI.yml)\n[![CD](https://github.com/afnanenayet/diffsitter/actions/workflows/CD.yml/badge.svg)](https://github.com/afnanenayet/diffsitter/actions/workflows/CD.yml)\n[![codecov](https://codecov.io/gh/afnanenayet/diffsitter/branch/main/graph/badge.svg?token=GBTJGXEXOS)](https://codecov.io/gh/afnanenayet/diffsitter)\n[![crates version](https://img.shields.io/crates/v/diffsitter)](https://crates.io/crates/diffsitter)\n[![GitHub release (latest by date)](https://img.shields.io/github/v/release/afnanenayet/diffsitter)](https://github.com/afnanenayet/diffsitter/releases/latest)\n![downloads](https://img.shields.io/crates/d/diffsitter)\n[![license](https://img.shields.io/github/license/afnanenayet/diffsitter)](./LICENSE)\n\n[![asciicast](https://asciinema.org/a/joEIfP8XoxUhZKXEqUD8CEP7j.svg)](https://asciinema.org/a/joEIfP8XoxUhZKXEqUD8CEP7j)\n\n## Disclaimer\n\n`diffsitter` is very much a work in progress and nowhere close to production\nready (yet). Contributions are always welcome!\n\n## Summary\n\n`diffsitter` creates semantically meaningful diffs that ignore formatting\ndifferences like spacing. It does so by computing a diff on the AST (abstract\nsyntax tree) of a file rather than computing the diff on the text contents of\nthe file.\n\n`diffsitter` uses the parsers from the\n[tree-sitter](https://tree-sitter.github.io/tree-sitter) project to parse\nsource code. As such, the languages supported by this tool are restricted to the\nlanguages supported by tree-sitter.\n\n`diffsitter` supports the following languages:\n\n* Bash\n* C#\n* C++\n* CSS\n* Go\n* Java\n* OCaml\n* PHP\n* Python\n* Ruby\n* Rust\n* Typescript/TSX\n* HCL\n\n## Examples\n\nTake the following files:\n\n[`a.rs`](test_data/short/rust/a.rs)\n\n```rust\nfn main() {\n    let x = 1;\n}\n\nfn add_one {\n}\n```\n\n[`b.rs`](test_data/short/rust/b.rs)\n\n```rust\nfn\n\n\n\nmain\n\n()\n\n{\n}\n\nfn addition() {\n}\n\nfn add_two() {\n}\n```\n\nThe standard output from `diff` will get you:\n\n```text\n1,2c1,12\n\u003c fn main() {\n\u003c     let x = 1;\n---\n\u003e fn\n\u003e\n\u003e\n\u003e\n\u003e main\n\u003e\n\u003e ()\n\u003e\n\u003e {\n\u003e }\n\u003e\n\u003e fn addition() {\n5c15\n\u003c fn add_one {\n---\n\u003e fn add_two() {\n```\n\nYou can see that it picks up the formatting differences for the `main`\nfunction, even though they aren't semantically different.\n\nCheck out the output from `diffsitter`:\n\n```\ntest_data/short/rust/a.rs -\u003e test_data/short/rust/b.rs\n======================================================\n\n9:\n--\n+ }\n\n11:\n---\n+ fn addition() {\n\n1:\n--\n-     let x = 1;\n\n14:\n---\n+ fn add_two() {\n\n4:\n--\n- fn add_one {\n```\n\n*Note: the numbers correspond to line numbers from the original files.*\n\nYou can also filter which tree sitter nodes are considered in the diff through\nthe config file.\n\nSince it uses the AST to calculate the difference, it knows that the formatting\ndifferences in `main` between the two files isn't a meaningful difference, so\nit doesn't show up in the diff.\n\n`diffsitter` has some nice (terminal aware) formatting too:\n\n![screenshot of rust diff](assets/rust_example.png)\n\nIt also has extensive logging if you want to debug or see timing information:\n\n![screenshot of rust diff with logs](assets/rust_example_logs.png)\n\n### Node filtering\n\nYou can filter the nodes that are considered in the diff by setting\n`include_nodes` or `exclude_nodes` in the config file. `exclude_nodes` always\ntakes precedence over `include_nodes`, and the type of a node is the `kind`\nof a tree-sitter node. The `kind` directly corresponds to whatever is reported\nby the tree-sitter API, so this example may occasionally go out of date.\n\nThis feature currently only applies to leaf nodes, but we could exclude nodes\nrecursively if there's demand for it.\n\n```json5\n\"input-processing\": {\n    // You can exclude different tree sitter node types - this rule takes precedence over `include_kinds`.\n    \"exclude_kinds\": [\"string_content\"],\n    // You can specifically allow only certain tree sitter node types\n    \"include_kinds\": [\"method_definition\"],\n}\n```\n\n## Installation\n\n\u003ca href=\"https://repology.org/project/diffsitter/versions\"\u003e\n  \u003cimg src=\"https://repology.org/badge/vertical-allrepos/diffsitter.svg\" alt=\"Packaging status\" align=\"right\"\u003e\n\u003c/a\u003e\n\n### Published binaries\n\nThis project uses Github actions to build and publish binaries for each tagged\nrelease. You can download binaries from there if your platform is listed. We\npublish [nightly releases](https://github.com/afnanenayet/diffsitter/releases/tag/nightly)\nas well as tagged [stable releases](https://github.com/afnanenayet/diffsitter/releases/latest).\n\n### Cargo\n\nYou can build from source with `cargo` using the following command:\n\n```sh\ncargo install diffsitter --bin diffsitter\n```\n\nIf you want to generate completion files and other assets you can install the\n`diffsitter_completions` binary with the following command:\n\n```sh\ncargo install diffsitter --bin diffsitter_completions\n```\n\n### Homebrew\n\nYou can use my tap to install diffsitter:\n\n```sh\nbrew tap afnanenayet/tap\nbrew install diffsitter\n# brew install afnanenayet/tap/diffsitter\n```\n\n### Arch Linux (AUR)\n\n@samhh has packaged diffsitter for arch on the AUR. Use your favorite AUR\nhelper to install [`diffsitter-bin`](https://aur.archlinux.org/packages/diffsitter-bin/).\n\n### Alpine Linux\n\nInstall package [diffsitter](https://pkgs.alpinelinux.org/packages?name=diffsitter) from the Alpine Linux repositories (on v3.16+ or Edge):\n\n```sh\napk add diffsitter\n```\n\nTree-sitter grammars are packaged separately (search for [tree-sitter-\\*](https://pkgs.alpinelinux.org/packages?name=tree-sitter-*\u0026arch=x86_64)).\nYou can install individual packages you need or the virtual package `tree-sitter-grammars` to install all of them.\n\n### Building with Docker\n\nWe also provide a Docker image that builds diffsitter using the standard Rust\nbase image. It separates the compilation stage from the run stage, so you can\nbuild it and run with the following command (assuming you have Docker installed\non your system):\n\n```sh\ndocker build -t diffsitter .\ndocker run -it --rm --name diffsitter-interactive diffsitter\n```\n\n## Usage\n\nFor detailed help you can run `diffsitter --help` (`diffsitter -h` provides\nbrief help messages).\n\nYou can configure file associations and formatting options for `diffsitter`\nusing a config file. If a config is not supplied, the app will use the default\nconfig, which you can see with `diffsitter dump-default-config`. It will\nlook for a config at `${XDG_HOME:-$HOME}/.config/diffsitter/config.json5` on\nmacOS and Linux, and the standard directory for Windows. You can also refer to\nthe [sample config](/assets/sample_config.json5).\n\nYou can override the default config path by using the `--config` flag or set\nthe `DIFFSITTER_CONFIG` environment variable.\n\n*Note: the tests for this crate check to make sure the provided sample config\nis a valid config.*\n\n### Git integration\n\nTo see the changes to the current git repo in diffsitter, you can add\nthe following to your repo's `.git/config` and run `git difftool`.\n\n```\n[diff]\n        tool = diffsitter\n\n[difftool]\n        prompt = false\n\n[difftool \"diffsitter\"]\n        cmd = diffsitter \"$LOCAL\" \"$REMOTE\"\n```\n\n### Shell Completion\n\nYou can generate shell completion scripts using the binary using the\n`gen-completion` subcommand. This will print the shell completion script for a\ngiven shell to `STDOUT`.\n\nYou should use the help text for the most up to date usage information, but\ngeneral usage would look like this:\n\n```sh\ndiffsitter gen-completion bash \u003e completion.bash\n```\n\nWe currently support the following shells (via `clap_complete`):\n\n* Bash\n* Zsh\n* Fish\n* Elvish\n* Powershell\n\n## Dependencies\n\n`diffsitter` is usually compiled as a static binary, so the `tree-sitter`\ngrammars/libraries are baked into the binary as static libraries. There is an\noption to build with support for dynamic libraries which will look for shared\nlibrary files in the user's default library path. This will search for\nlibrary files of the form `libtree-sitter-{lang}.{ext}`, where `lang` is the\nlanguage that the user is trying to diff and `ext` is the platform-specific\nextension for shared library files (`.so`, `.dylib`, etc). The user can\noverride the dynamic library file for each language in the config as such:\n\n```json5\n{\n    \"grammar\": {\n        // You can specify the dynamic library names for each language\n        \"dylib-overrides\": {\n            // with a filename\n            \"rust\": \"libtree-sitter-rust.so\",\n            // with an absolute path\n            \"c\": \"/usr/lib/libtree-sitter-c.so\",\n            // with a relative path\n            \"cpp\": \"../libtree-sitter-c.so\",\n        },\n    }\n}\n```\n\n*The above excerpt was taken from the\n[sample config](/assets/sample_config.json5).*\n\n## MCP Server (AI Code Navigation)\n\ndiffsitter includes an [MCP](https://modelcontextprotocol.io) server that\nexposes tree-sitter AST navigation as tools for AI coding assistants. This\ngives tools like [Claude Code](https://claude.ai/code) structural\nunderstanding of your code — jumping to definitions by name, listing symbols,\ninspecting scopes, and running tree-sitter queries — across all 14+ supported\nlanguages.\n\n### Tools\n\n| Tool | Description |\n|------|-------------|\n| `parse_file` | Parse a file and return its top-level AST structure |\n| `list_symbols` | List all functions, classes, structs, traits, enums, constants |\n| `get_definition` | Get the full source text of a symbol by name |\n| `get_children_of` | Get methods/fields inside a class, impl block, or module |\n| `get_node_at_position` | Get the deepest AST node at a line/column |\n| `get_scope` | Get the enclosing scope at a position with full parent chain |\n| `navigate` | Move through the AST: parent, first_child, next_sibling, prev_sibling |\n| `query` | Run a raw tree-sitter S-expression query with captures |\n\n### Setup\n\nBuild the MCP server binary:\n\n```sh\ncargo build --release --features mcp-server --bin tree-sitter-mcp\n```\n\nOr install from crates.io:\n\n```sh\ncargo install diffsitter --features mcp-server --bin tree-sitter-mcp\n```\n\n#### Claude Code\n\nRegister the server with Claude Code:\n\n```sh\n# Register the binary as an MCP server\nclaude mcp add tree-sitter-mcp -- /path/to/tree-sitter-mcp\n\n# Or use the bundled plugin for development (loads for one session)\nclaude --plugin-dir ./plugins/tree-sitter-mcp\n```\n\nOnce registered, Claude Code can use the tools automatically. For example,\nasking \"what functions are defined in src/diff.rs?\" will use `list_symbols`\ninstead of reading the entire file.\n\n#### Other MCP clients\n\nThe server communicates over stdio using\n[JSON-RPC](https://www.jsonrpc.org/specification). Any MCP-compatible client\ncan use it by launching the binary as a subprocess:\n\n```json\n{\n  \"mcpServers\": {\n    \"tree-sitter-mcp\": {\n      \"command\": \"/path/to/tree-sitter-mcp\"\n    }\n  }\n}\n```\n\n### Example queries\n\nThe MCP server understands language grammar, not just text. Where `grep` finds\nstring patterns, tree-sitter-mcp finds *syntactic* patterns — it knows the\ndifference between a function called `test` and a `#[test]` attribute.\n\n#### Symbol discovery\n\n```\n# \"What's defined in this file?\"\nlist_symbols  →  file_path: \"src/diff.rs\"\n\n# \"What methods does the Renderer trait define?\"\nget_children_of  →  file_path: \"src/render/mod.rs\", symbol_name: \"Renderer\"\n\n# \"Show me the signature of generate_ast_vector_data without reading the whole file\"\nget_definition  →  file_path: \"src/lib.rs\", symbol_name: \"generate_ast_vector_data\"\n```\n\n#### Scope \u0026 context\n\n```\n# \"What function contains line 145 of src/diff.rs? Show me the full parent chain.\"\nget_scope  →  file_path: \"src/diff.rs\", line: 145, column: 0\n\n# \"What's the AST node at this position? Navigate to its parent, then next sibling.\"\nget_node_at_position  →  file_path: \"src/lib.rs\", line: 50, column: 10\nnavigate  →  file_path: \"src/lib.rs\", line: 50, column: 10, direction: \"parent\"\n```\n\n#### Tree-sitter queries (the real power)\n\nThe `query` tool accepts [tree-sitter S-expression\npatterns](https://tree-sitter.github.io/tree-sitter/using-parsers/queries/index.html)\nfor structural code search:\n\n```\n# Find all unsafe blocks\nquery  →  file_path: \"src/diff.rs\"\n          pattern: \"(unsafe_block) @unsafe\"\n\n# Find all impl blocks for a specific type\nquery  →  file_path: \"src/config.rs\"\n          pattern: '(impl_item type: (type_identifier) @name (#eq? @name \"Config\")) @impl'\n\n# Find all functions that return a Result\nquery  →  file_path: \"src/diff.rs\"\n          pattern: '(function_item\n            name: (identifier) @name\n            return_type: (generic_type\n              type: (type_identifier) @ret (#eq? @ret \"Result\"))) @fn'\n\n# Find all #[test] functions\nquery  →  file_path: \"src/diff.rs\"\n          pattern: '(attribute_item (attribute (identifier) @attr (#eq? @attr \"test\"))) @test'\n\n# Find all closures\nquery  →  file_path: \"src/input_processing.rs\"\n          pattern: \"(closure_expression) @closure\"\n```\n\n## Questions, Bugs, and Support\n\nIf you notice any bugs, have any issues, want to see a new feature, or just\nhave a question, feel free to open an\n[issue](https://github.com/afnanenayet/diffsitter/issues) or create a\n[discussion post](https://github.com/afnanenayet/diffsitter/discussions).\n\nIf you file an issue, it would be preferable that you include a minimal example\nand/or post the log output of `diffsitter` (which you can do by adding the\n`-d/--debug` flag).\n\n## Development\n\n### Prerequisites\n\n- **Rust toolchain** (MSRV 1.85.1, edition 2024) — install via [rustup](https://rustup.rs/)\n- **C99+ compiler** and **C++14+ compiler** — required to compile tree-sitter grammars (Apple Clang, GCC, or LLVM all work)\n- **Git submodules** initialized — the build compiles tree-sitter grammars from vendored sources in `grammars/`\n\n```sh\n# Clone with submodules\ngit clone --recurse-submodules https://github.com/afnanenayet/diffsitter.git\n\n# Or initialize submodules in an existing checkout\ngit submodule update --init --recursive\n```\n\n#### Recommended tools\n\nThese are not required for building diffsitter itself, but are used for development and CI:\n\n| Tool | Install | Purpose |\n|------|---------|---------|\n| [cargo-nextest](https://nexte.st) | `cargo install cargo-nextest` | Test runner (used in CI, configured in `.config/nextest.toml`) |\n| [cargo-insta](https://insta.rs) | `cargo install cargo-insta` | Snapshot test review TUI |\n| [pre-commit](https://pre-commit.com) | `pip install pre-commit` | Git hook manager for formatting/linting |\n| [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz) | `cargo install cargo-fuzz` | Fuzz testing (requires nightly Rust) |\n\n### Building\n\n```sh\ncargo build                                                            # Default: static grammars\ncargo build --no-default-features --features dynamic-grammar-libs      # Dynamic grammar loading\ncargo build --profile production                                       # Release build with LTO + strip\ncargo build --features mcp-server --bin tree-sitter-mcp                # MCP server binary\n```\n\nThe default build compiles all tree-sitter grammars from C/C++ source into the binary. Use `cargo check` for a fast feedback loop that skips grammar compilation.\n\n### Testing\n\n```sh\ncargo nextest run --all-features                  # All tests (preferred)\ncargo test --all-features                         # Fallback without nextest\ncargo test --doc --all-features                   # Doc tests only (nextest doesn't run these)\ncargo insta review                                # Review/accept changed snapshots\n```\n\n### Linting\n\n```sh\ncargo fmt --all -- --check                        # Check formatting\ncargo fmt --all                                   # Auto-format\ncargo clippy --all-targets --all-features -- -D warnings   # Lint (matches CI)\n```\n\n### Benchmarks\n\n```sh\ncargo bench                                       # Run all criterion benchmarks\ncargo bench -- \u003cfilter\u003e                           # Run benchmarks matching filter\n```\n\nBenchmarks use [criterion](https://github.com/bheisler/criterion.rs) and cover parsing, cache performance, symbol listing, navigation, and query execution. Results are written to `target/criterion/`.\n\n### Fuzz testing\n\n```sh\ncargo +nightly fuzz list                          # List available fuzz targets\ncargo +nightly fuzz run fuzz_parse_and_navigate -- -max_total_time=60\ncargo +nightly fuzz run fuzz_query -- -max_total_time=60\ncargo +nightly fuzz run fuzz_node_to_info -- -max_total_time=60\n```\n\n### Feature flags\n\n| Flag | Default | Description |\n|------|---------|-------------|\n| `static-grammar-libs` | Yes | Compiles tree-sitter grammars into the binary |\n| `dynamic-grammar-libs` | No | Loads grammars from system shared libraries at runtime |\n| `better-build-info` | No | Extended build metadata via shadow-rs |\n| `mcp-server` | No | Builds `tree-sitter-mcp` binary (adds `rmcp`, `tokio`, `schemars`) |\n\n## Contributing\n\nSee [CONTRIBUTING.md](docs/CONTRIBUTING.md).\n\n## Similar Projects\n\n* [difftastic](https://github.com/Wilfred/difftastic)\n* [locust](https://github.com/bugout-dev/locust)\n* [gumtree](https://github.com/GumTreeDiff/gumtree)\n* [diffr](https://github.com/mookid/diffr)\n* [delta](https://github.com/dandavison/delta)\n* [Semantic Diff Tool](https://www.sdt.dev)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fafnanenayet%2Fdiffsitter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fafnanenayet%2Fdiffsitter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fafnanenayet%2Fdiffsitter/lists"}