diff --git a/src/lai_automation/git.py b/src/lai_automation/git.py index 2013fce..6e8da75 100644 --- a/src/lai_automation/git.py +++ b/src/lai_automation/git.py @@ -115,6 +115,13 @@ class GitWorkspace: deleted += int(remove) if remove.isdigit() else 0 return ChangeSummary(tuple(files), added, deleted) + def diff(self, limit: int = 12_000) -> str: + output = self._git("diff", "--no-ext-diff", "--unified=3").stdout + if len(output) <= limit: + return output + half = limit // 2 + return output[:half] + "\n...[diff middle omitted]...\n" + output[-half:] + def commit(self, report_id: int) -> str: self._git("add", "--all") self._git("commit", "-m", f"Fix failure report #{report_id}") diff --git a/src/lai_automation/worker.py b/src/lai_automation/worker.py index 4048a8a..fe18eb5 100644 --- a/src/lai_automation/worker.py +++ b/src/lai_automation/worker.py @@ -120,6 +120,7 @@ class AutomationWorker: report.id, attempt, ) + discarded_diff = workspace.diff() workspace.restore_checkpoint(valid_checkpoint) restored_state = ( "The last syntax-valid changes were restored. Correct them without recreating " @@ -131,7 +132,8 @@ class AutomationWorker: report, RuntimeError( f"{exception}\n{restored_state} Make smaller edits and run php -l on each " - "changed PHP file." + "changed PHP file. Reconstruct the intended application fix from this discarded " + f"patch excerpt, correcting its syntax:\n{discarded_diff}" ), ) continue diff --git a/tests/test_git.py b/tests/test_git.py index eefd575..b0cc6b5 100644 --- a/tests/test_git.py +++ b/tests/test_git.py @@ -83,4 +83,25 @@ def test_checkpoint_restores_last_valid_changes(tmp_path) -> None: workspace.restore_checkpoint(checkpoint) assert tracked.read_text() == " None: + workspace = GitWorkspace(settings(workspace_root=tmp_path), 5) + workspace.path.mkdir() + subprocess.run(["git", "init"], cwd=workspace.path, check=True, capture_output=True) + tracked = workspace.path / "service.php" + tracked.write_text(" None: Mock(returncode=0, stdout="No syntax errors", stderr=""), Mock(returncode=0, stdout="No syntax errors", stderr=""), ] + workspace.diff.return_value = "--- app/Service.php\n+++ app/Service.php\n+intended fix" workspace.checkpoint.return_value = "valid-checkpoint" workspace.commit.return_value = "commit-id" @@ -167,6 +168,7 @@ def test_php_syntax_failure_discards_attempt_before_full_validation() -> None: assert worker.agent.run.call_count == 2 assert "unexpected token" in worker.agent.run.call_args_list[1].args[1] assert "clean repository" in worker.agent.run.call_args_list[1].args[1] + assert "intended fix" in worker.agent.run.call_args_list[1].args[1] workspace.restore_checkpoint.assert_called_once_with(None) worker._run_tests.assert_called_once() @@ -207,6 +209,7 @@ def test_malformed_retry_restores_last_syntax_valid_attempt() -> None: Mock(returncode=0, stdout="No syntax errors", stderr=""), Mock(returncode=0, stdout="No syntax errors", stderr=""), ] + workspace.diff.return_value = "--- app/Service.php\n+++ app/Service.php\n+application repair" workspace.checkpoint.side_effect = ["checkpoint-1", "checkpoint-3"] workspace.commit.return_value = "commit-id" @@ -216,6 +219,7 @@ def test_malformed_retry_restores_last_syntax_valid_attempt() -> None: assert worker.agent.run.call_count == 3 workspace.restore_checkpoint.assert_called_once_with("checkpoint-1") assert "last syntax-valid changes were restored" in worker.agent.run.call_args_list[2].args[1] + assert "application repair" in worker.agent.run.call_args_list[2].args[1] assert worker._run_tests.call_count == 2