mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
Audit caught four bugs that the byte-equal parity harness can't catch on its own — both Python and Rust were faithfully emitting the buggy output. Fixed in lockstep so parity is maintained while the underlying behavior is now correct on inputs the existing 20 fixtures didn't exercise. # The four bugs (each fixed in both Python and Rust) 1. Renames silently dropped from output. Parser captured `is_renamed=True` but the emitter never emitted ANY rename markers. Output of a rename looked exactly like a plain modification of the old path. Fix: capture `rename from` / `rename to` / `similarity index N%` / `dissimilarity index N%` / `copy from` / `copy to` lines in a new `rename_lines` field on `DiffFile`; emit them after `diff --git` in canonical git ordering. 2. Combined diff hunks (`@@@`) silently dropped. Hunk-header regex only matched `@@`, so 3-way merge hunks had `current_hunk` never set and ALL their content fell through to the no-op branch. Fix in Python: regex switched to `^(@@+) ... \1` (backreferences match any number of `@`s on each side). Fix in Rust: alternation over `@@`, `@@@`, `@@@@` since `regex` is RE2-based and rejects backreferences. n>3 octopus merges still fall through; rare in practice. 3. `\ No newline at end of file` markers can be context-trimmed away. Treated as ordinary "other" lines — if more than `max_context_lines` from a `+`/`-` change, dropped. Round-trip-breaking for patches; can change whether the trailing line has a newline. Fix: in `_reduce_context`, force-add any line starting with `\` to the keep set regardless of distance. 4. Pre-diff content silently dropped. Anything before the first `diff --git` — commit messages from `git log -p`, email headers from `git format-patch`, fork-and-rebase metadata — was discarded. Fix: `_parse_diff` now returns `(pre_diff_lines, files)`; `format_output` prepends pre-diff content verbatim when present. # Hidden parity bug found during the work `_compress_files` constructed a fresh `DiffFile` from the parsed one but only copied a subset of the fields by name. The new `rename_lines` and `original_*_line` fields were silently dropped here, so the parser populated them correctly but the emitter saw an empty `rename_lines` list. Caught by writing a real test instead of a smoke test — the smoke test passed because it hit the no-diff-found short-circuit, not the parser/emitter pipeline. Constructor now copies all fields explicitly. # Parity status - Existing 20 fixtures: still byte-equal between fixed Python and fixed Rust. None of them exercised the buggy paths. - 4 NEW fixtures recorded against fixed Python, exercising each bug-fix path: rename, 3-way combined diff, `\ No newline` marker far from changes, pre-diff commit headers. All 4 byte-equal between Python and Rust. - Parity harness: total=24 matched=24 skipped=0 diffed=0. # Observability Some normalizations remain parity-bound (file mode `100644` hardcode, `Binary files differ` simplification). Those are surfaced in `DiffCompressorStats::file_mode_normalizations` / `binary_files_simplified` (Rust) and via `logger.warning` (Python's new `_log_loss_signals` helper, called once per compress). # Tests - Python: 4 new test classes (11 tests) covering rename markers, combined diffs, no-newline preservation, pre-diff content. Edge case: no pre-diff content must NOT add a leading blank line. - Rust: 4 new `bugfix_*` unit tests with the same scenarios. - Existing Python tests calling `_parse_diff` directly were updated for the new `(pre_diff, files)` tuple return. # Verification - Python: 37/37 tests pass (was 26). - Rust: 16/16 transforms tests; 60/60 workspace unit tests; 5/5 proptests; 1/1 doctest. - Parity: 24/24 byte-equal. - `cargo fmt --check` clean; `cargo clippy --workspace --all-targets -- -D warnings` clean.
26 lines
1.2 KiB
JSON
26 lines
1.2 KiB
JSON
{
|
|
"config": {
|
|
"always_keep_additions": true,
|
|
"always_keep_deletions": true,
|
|
"enable_ccr": true,
|
|
"max_context_lines": 2,
|
|
"max_files": 20,
|
|
"max_hunks_per_file": 10,
|
|
"min_lines_for_ccr": 50
|
|
},
|
|
"input": "diff --git a/merge_target.py b/merge_target.py\n--- a/merge_target.py\n+++ b/merge_target.py\n@@@ -1,5 -1,5 +1,6 @@@\n unchanged_a\n unchanged_b\n- old_from_branch_1\n -old_from_branch_2\n++new_in_merge\n +new_added_too\n unchanged_c\n\n# bugfix:combined-diff-3way",
|
|
"input_sha256": "da9afe00e9846ce4c9da0a08cc37a7444b68ed476b9e2ac86cfa67d6b309903e",
|
|
"output": {
|
|
"additions": 0,
|
|
"cache_key": null,
|
|
"compressed": "diff --git a/merge_target.py b/merge_target.py\n--- a/merge_target.py\n+++ b/merge_target.py\n@@@ -1,5 -1,5 +1,6 @@@\n unchanged_a\n unchanged_b\n- old_from_branch_1\n -old_from_branch_2\n++new_in_merge\n +new_added_too\n unchanged_c\n\n# bugfix:combined-diff-3way",
|
|
"compressed_line_count": 13,
|
|
"deletions": 0,
|
|
"files_affected": 0,
|
|
"hunks_kept": 0,
|
|
"hunks_removed": 0,
|
|
"original_line_count": 13
|
|
},
|
|
"recorded_at": "2026-04-25T23:19:00.819971+00:00",
|
|
"transform": "diff_compressor"
|
|
}
|