Skip to main content

debug-diff

Diagnose unexpected diff output by tracing the pipeline from parsing through AST processing to hunk generation. Use when diffsitter produces wrong or surprising results.

Ir para a instalação

Informações da origem

Repositório
afnanenayet/diffsitter
Última atividade na origem
26 de março de 2026 às 20:56
Idioma detectado do SKILL.md
inglês
Estrelas
2.403
Forks
53

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
debug-diff
description
Diagnose unexpected diff output by tracing the pipeline from parsing through AST processing to hunk generation. Use when diffsitter produces wrong or surprising results.
allowed-tools
Read, Grep, Glob, Bash
user-invocable
true
argument-hint
[description] What unexpected behavior are you seeing?
# Debugging Unexpected Diff Output in diffsitter When diffsitter produces wrong or surprising results, trace the issue through the pipeline. The diff pipeline has 7 stages, and problems can originate at any of them. Ask the user for the following information if not already provided: - The two files being compared (or representative snippets) - The language/file extension - Any custom config being used - What output they expected vs what they got ## Pipeline Overview The full pipeline (see `src/bin/diffsitter.rs`, function `run_diff`): ``` 1. Language detection 2. Tree-sitter parse (file -> AST Tree) 3. AST leaf extraction (Tree -> Vec<VectorLeaf>) 4. Node filtering (exclude_kinds / include_kinds) 5. Grapheme splitting + whitespace stripping (VectorLeaf -> Vec<Entry>) 6. Myers diff (Vec<Entry> x 2 -> Vec<EditType>) 7. Hunk assembly (Vec<EditType> -> RichHunks) 8. Rendering (RichHunks -> terminal output) ``` ## Stage-by-Stage Debugging ### Stage 1: Language Detection **File**: `src/parse.rs`, function `resolve_language_str` The language is resolved from the file extension via the `FILE_EXTS` phf_map, with optional user overrides from `GrammarConfig.file_associations`. **Common issues:** - Extension not mapped (e.g., `.jsx` maps to `"tsx"`, `.h` maps to `"c"` not `"cpp"`) - User override in config shadowing the default **Debug**: Check what language is resolved: ```sh # Enable debug logging to see language resolution RUST_LOG=debug diffsitter old_file new_file 2>&1 | grep -i "deduced language" ``` Or read `FILE_EXTS` in `src/parse.rs` to verify the extension mapping. ### Stage 2: Tree-sitter Parse **File**: `src/parse.rs`, function `parse_file` Creates a `Parser`, sets the language, reads the file to a string, and calls `parser.parse(&text, None)`. **Common issues:** - Grammar ABI version mismatch (`AbiOutOfRange` error) -- the grammar was compiled against an incompatible tree-sitter version. - Parse failure returns `None` from `parser.parse()`, resulting in a `TSParseFailure` error. - File encoding issues (tree-sitter expects UTF-8). **Debug**: If parsing succeeds but the tree looks wrong, the grammar itself may have a bug for that language construct. Check tree-sitter's own playground or CLI to inspect the AST. ### Stage 3: AST Leaf Extraction **File**: `src/input_processing.rs`, function `build` Recursively walks the tree via `node.children(&mut cursor)`. Collects leaf nodes (nodes with `child_count() == 0`) OR nodes whose `kind()` matches a `pseudo_leaf_types` entry. **Key behaviors:** - Empty byte ranges are skipped (`node.byte_range().is_empty()`) - Nodes that are pure newlines (after removing `\n`, `\r`, `\r\n`) are skipped -- this is a workaround for the Go parser - Pseudo-leaf types (configured in `input_processing.pseudo_leaf_types`) treat certain non-leaf nodes as leaves. Default: `{"markdown": {"inline"}}`. This is critical for text-heavy documents. **Common issues:** - Missing diffs in markdown/prose: Check if `pseudo_leaf_types` includes the right node types for that language. Without `"inline"` for markdown, large text blocks are treated as single atoms. - Unexpected nodes included: The grammar may expose more leaf nodes than expected (e.g., punctuation, delimiters). ### Stage 4: Node Filtering **File**: `src/input_processing.rs`, method `TreeSitterProcessor::should_include_node` Filters nodes based on `exclude_kinds` and `include_kinds` from the config: - `exclude_kinds` takes precedence: if a node's `kind()` is in this set, it is excluded. - `include_kinds`: if set, only nodes whose `kind()` is in this set are included (unless also excluded). - If neither is set, all nodes pass through. **Common issues:** - User config has `exclude_kinds` or `include_kinds` that filters out relevant nodes. - The node `kind()` string doesn't match what the user expects (tree-sitter kind names are grammar-specific). **Debug**: Check what node kinds exist for a language using tree-sitter's node types. The `kind()` strings come from the grammar definition. ### Stage 5: Grapheme Splitting and Whitespace Stripping **File**: `src/input_processing.rs`, method `VectorLeaf::split_on_graphemes` If `split_graphemes` is enabled (default: true), each leaf's text is split into individual Unicode graphemes, each becoming its own `Entry` with precise row/column positions. If `strip_whitespace` is enabled (default: true), whitespace-only graphemes are skipped. **Common issues:** - **Whitespace-only diffs not showing**: `strip_whitespace: true` (the default) means pure whitespace/indentation changes are invisible. This is by design for AST-based diffing. If the user wants to see whitespace changes, they need `"strip-whitespace": false` in their config. - **Performance with large files**: `split_graphemes: true` generates many entries for large text nodes. Setting `"split-graphemes": false` trades granularity for speed. - **Line position bugs**: The grapheme splitter tracks row/column positions. If `line_offset == 0`, it offsets from the node's `start_position().column`. Otherwise it resets the column to `idx` (the byte offset within the line). Bugs here manifest as incorrect column highlighting in the rendered output. ### Stage 6: Myers Diff **File**: `src/diff.rs`, struct `Myers`, method `diff` Implements the classic Myers "An O(ND) Difference Algorithm" with divide-and-conquer via middle snake. Takes two `&[Entry]` slices, produces `Vec<EditType<&Entry>>` where `EditType` is either `Addition` or `Deletion`. **Key optimizations:** - Common prefix/suffix are skipped before running the main algorithm (via `common_prefix_len` / `common_suffix_len` which use `get_unchecked` for speed). - `Entry` equality is based on `kind_id` AND `text` (see the `PartialEq` impl) -- two nodes are equal only if they have the same tree-sitter kind AND identical text content. **Common issues:** - **Too many diffs reported**: If entries that should be equal are not, check if `kind_id` differs between them. Two nodes with the same text but different grammar kinds (e.g., `identifier` vs `type_identifier` in Rust) are considered different. - **No diffs reported when expected**: If entries that should differ are comparing as equal, verify the text content after whitespace stripping. ### Stage 7: Hunk Assembly **File**: `src/diff.rs`, struct `RichHunksBuilder` Converts the flat edit script into grouped hunks. Each `EditType::Addition` becomes `DocumentType::New`, each `EditType::Deletion` becomes `DocumentType::Old`. Consecutive edits on adjacent lines are grouped into the same `Hunk`. Non-adjacent edits start a new hunk. **Common issues:** - **`PriorLine` or `PriorColumn` errors**: These indicate the edit script produced entries in non-ascending order, which is a bug in the diff or input processing stage. - **Hunks splitting unexpectedly**: If edits on adjacent lines end up in separate hunks, there may be a gap in line numbers caused by filtered-out nodes. ### Stage 8: Rendering **File**: `src/render/unified.rs` (for unified renderer), `src/render/json.rs` (for JSON) **Common issues with unified renderer:** - **Line index out of bounds**: The `print_hunk` method accesses `lines[line_index]`. If `line_index >= lines.len()`, it logs an error and skips the line (in release) or panics via `debug_assert!` (in debug). - **Column range panics**: `print_line` indexes into the line text using byte column ranges from entries. If the entry positions don't align with the actual text bytes, this can panic with a slice bounds error. - **Missing terminal colors**: If output is piped, `term_info` may be `None`. The unified renderer still works but won't have terminal width info for title formatting. ## Quick Diagnostic Commands ```sh # Run with full debug logging RUST_LOG=trace diffsitter old_file new_file 2>debug.log # Output as JSON to inspect raw diff data diffsitter --renderer json old_file new_file | jq . # Force a specific language diffsitter --file-type python old_file new_file # Run without config to eliminate config issues diffsitter --no-config old_file new_file ``` ## Common Failure Modes Summary | Symptom | Likely Stage | Check | |---------|-------------|-------| | "Unsupported extension" error | 1 (Language detection) | Extension in `FILE_EXTS`? | | Parse error / empty AST | 2 (Tree-sitter parse) | Grammar ABI compatible? File is valid UTF-8? | | No diff when content clearly changed | 5 (Whitespace stripping) | `strip_whitespace` removing the changes? | | Missing diffs in markdown/prose | 3 (Leaf extraction) | `pseudo_leaf_types` configured for language? | | Diffs include too many trivial nodes | 4 (Node filtering) | Set `exclude_kinds` for noise (e.g., `"comment"`, `"string"`) | | Wrong columns highlighted | 5 (Grapheme splitting) | Grapheme position calculation bug | | Panic on "index out of bounds" | 7/8 (Hunk assembly / Rendering) | Line index mismatch between entry positions and actual text | | Extremely slow on large files | 5/6 (Graphemes / Myers) | Try `"split-graphemes": false` or check if diff is O(ND) worst-case |
Ver no GitHub