From f113f10f363980eea84436f8f94053bb6ee2d630 Mon Sep 17 00:00:00 2001 From: Andrew Chen <48723787+chuenchen309@users.noreply.github.com> Date: Tue, 21 Jul 2026 17:48:04 +0800 Subject: [PATCH] fix(workspace-changes): count diff body lines starting with "-- "/"++ " (#4303) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `_count_diff_lines` skipped every line starting with "+++ "/"--- " to drop difflib's two file headers. But a deleted hunk-body line whose content begins with "-- " (e.g. a SQL comment `-- get users`) becomes the diff line `--- get users`, and an added `++ ...` line becomes `+++ ...`, so those were dropped too — undercounting the user-visible +N/-M change summary (WorkspaceChangeSummary, per-file WorkspaceFileChange, and the run-event string in recorder.py). `difflib.unified_diff` always emits the two headers first, so skip them by position (`lines[2:]`) instead. Hunk `@@` lines start with `@` and are still ignored. Numeric/plain diffs are unaffected. Co-authored-by: Claude Opus 4.8 --- .../deerflow/workspace_changes/diff.py | 12 ++++----- backend/tests/test_workspace_changes.py | 27 +++++++++++++++++++ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/backend/packages/harness/deerflow/workspace_changes/diff.py b/backend/packages/harness/deerflow/workspace_changes/diff.py index ef9d53067..a28c875f6 100644 --- a/backend/packages/harness/deerflow/workspace_changes/diff.py +++ b/backend/packages/harness/deerflow/workspace_changes/diff.py @@ -198,12 +198,12 @@ def _snapshot_text(file: FileSnapshot | None) -> str | None: def _count_diff_lines(lines: list[str]) -> tuple[int, int]: additions = 0 deletions = 0 - for line in lines: - # Unified-diff file headers are "+++ " / "--- " with a trailing space; - # a bare "+++"/"---" prefix would also swallow real content lines whose - # text begins with those sequences (e.g. an added line "+++foo"). - if line.startswith("+++ ") or line.startswith("--- "): - continue + # difflib.unified_diff always emits the two file headers ("--- a/…" then + # "+++ b/…") first; skip them by position. A prefix test can't tell them + # apart from a hunk-body line whose content starts with "-- "/"++ " (e.g. a + # deleted SQL comment "-- get users" becomes the diff line "--- get users"), + # which would then be dropped from the count. + for line in lines[2:]: if line.startswith("+"): additions += 1 elif line.startswith("-"): diff --git a/backend/tests/test_workspace_changes.py b/backend/tests/test_workspace_changes.py index 7648686b6..2ec7d868d 100644 --- a/backend/tests/test_workspace_changes.py +++ b/backend/tests/test_workspace_changes.py @@ -182,6 +182,33 @@ def test_count_diff_lines_ignores_only_real_headers(): assert deletions == 2 +def test_count_diff_lines_counts_content_starting_with_dashes_or_pluses(): + """Hunk-body lines whose content starts with '-- '/'++ ' must be counted. + + difflib prefixes a deleted line "-- get users" to "--- get users"; the old + prefix skip mistook that for a file header and dropped it, undercounting + deletions in the user-visible +N/-M summary. + """ + import difflib + + from deerflow.workspace_changes.diff import _count_diff_lines + + lines = list( + difflib.unified_diff( + ["SELECT 1", "-- get users"], + ["SELECT 1", "SELECT 2"], + fromfile="a/x.sql", + tofile="b/x.sql", + lineterm="", + ) + ) + + additions, deletions = _count_diff_lines(lines) + + assert additions == 1 + assert deletions == 1 + + def test_scan_workspace_roots_skips_excluded_directories(tmp_path): roots = _roots(tmp_path) workspace = roots[0].host_path