@@ -11,6 +11,7 @@ from pathlib import Path |
| 11 | 11 | from typing import Any, Literal |
| 12 | 12 | |
| 13 | 13 | from ..llm.base import Message, ToolCall |
| 14 | +from ..tools.fs_safety import StructuredPatchHunk, coerce_structured_patch_payload |
| 14 | 15 | from ..tools.shell_tools import BashTool |
| 15 | 16 | from .verification_observations import VerificationAttempt, verification_attempt_id |
| 16 | 17 | |
@@ -157,6 +158,7 @@ class DefinitionOfDone: |
| 157 | 158 | storage_path: str | None = None |
| 158 | 159 | last_verification_result: str | None = None |
| 159 | 160 | last_verification_signature: str | None = None |
| 161 | + last_failed_verification_issue_signature: str | None = None |
| 160 | 162 | verification_attempt_counter: int = 0 |
| 161 | 163 | active_verification_attempt_id: str | None = None |
| 162 | 164 | active_verification_attempt_number: int | None = None |
@@ -196,6 +198,9 @@ class DefinitionOfDone: |
| 196 | 198 | storage_path=data.get("storage_path"), |
| 197 | 199 | last_verification_result=data.get("last_verification_result"), |
| 198 | 200 | last_verification_signature=data.get("last_verification_signature"), |
| 201 | + last_failed_verification_issue_signature=data.get( |
| 202 | + "last_failed_verification_issue_signature" |
| 203 | + ), |
| 199 | 204 | verification_attempt_counter=int(data.get("verification_attempt_counter", 0)), |
| 200 | 205 | active_verification_attempt_id=data.get("active_verification_attempt_id"), |
| 201 | 206 | active_verification_attempt_number=( |
@@ -286,11 +291,8 @@ def record_successful_tool_call( |
| 286 | 291 | file_path = _resolve_touched_path(tool_call.arguments.get("file_path", "")) |
| 287 | 292 | if file_path: |
| 288 | 293 | _append_unique(dod.touched_files, file_path) |
| 289 | | - for hunk in tool_call.arguments.get("hunks", []): |
| 290 | | - if not isinstance(hunk, dict): |
| 291 | | - continue |
| 292 | | - old_lines = int(hunk.get("old_lines", 0)) |
| 293 | | - new_lines = int(hunk.get("new_lines", 0)) |
| 294 | + for hunk in _coerce_patch_hunks_for_accounting(tool_call.arguments): |
| 295 | + old_lines, new_lines = _patch_hunk_line_counts(hunk) |
| 294 | 296 | dod.line_changes += max(old_lines, new_lines) |
| 295 | 297 | elif tool_call.name == "bash": |
| 296 | 298 | command = str(tool_call.arguments.get("command", "")).strip() |
@@ -691,6 +693,42 @@ def _count_lines(content: str) -> int: |
| 691 | 693 | return content.count("\n") + 1 |
| 692 | 694 | |
| 693 | 695 | |
| 696 | +def _coerce_patch_hunks_for_accounting( |
| 697 | + arguments: dict[str, object], |
| 698 | +) -> list[dict[str, object] | StructuredPatchHunk]: |
| 699 | + for key in ("hunks", "structured_patch", "structuredPatch"): |
| 700 | + hunks = coerce_structured_patch_payload(arguments.get(key)) |
| 701 | + if hunks: |
| 702 | + return hunks |
| 703 | + return [] |
| 704 | + |
| 705 | + |
| 706 | +def _patch_hunk_line_counts( |
| 707 | + hunk: dict[str, object] | StructuredPatchHunk, |
| 708 | +) -> tuple[int, int]: |
| 709 | + if isinstance(hunk, StructuredPatchHunk): |
| 710 | + return hunk.old_lines, hunk.new_lines |
| 711 | + |
| 712 | + old_lines = _coerce_int(hunk.get("old_lines"), default=0) |
| 713 | + new_lines_value = hunk.get("new_lines") |
| 714 | + if isinstance(new_lines_value, list): |
| 715 | + new_lines = len(new_lines_value) |
| 716 | + old_start = _coerce_int(hunk.get("old_start"), default=0) |
| 717 | + old_end = _coerce_int(hunk.get("old_end"), default=old_start - 1) |
| 718 | + if old_lines <= 0 and old_start > 0: |
| 719 | + old_lines = max(0, old_end - old_start + 1) |
| 720 | + else: |
| 721 | + new_lines = _coerce_int(new_lines_value, default=0) |
| 722 | + return max(0, old_lines), max(0, new_lines) |
| 723 | + |
| 724 | + |
| 725 | +def _coerce_int(value: object, *, default: int) -> int: |
| 726 | + try: |
| 727 | + return int(value) # type: ignore[arg-type] |
| 728 | + except (TypeError, ValueError): |
| 729 | + return default |
| 730 | + |
| 731 | + |
| 694 | 732 | def _is_verification_command(command: str) -> bool: |
| 695 | 733 | command_lower = command.lower() |
| 696 | 734 | signals = ( |