diff --git a/src/lai_automation/agent.py b/src/lai_automation/agent.py index be04409..38fa79c 100644 --- a/src/lai_automation/agent.py +++ b/src/lai_automation/agent.py @@ -86,5 +86,5 @@ class AiderAgent: "--edit-format", "diff", "--timeout", - "60", + "180", ] diff --git a/src/lai_automation/prompt.py b/src/lai_automation/prompt.py index 4039cac..e117561 100644 --- a/src/lai_automation/prompt.py +++ b/src/lai_automation/prompt.py @@ -33,6 +33,15 @@ def build_task(report: FailureReport) -> str: response = redact(report.response) error = redact(report.error_message) sql = redact(report.sql_query) + tenant_scope = '' + if report.context.get('database_type') == 'customer': + tenant_scope = """ +Tenant routing fact: +The customer has already selected one tenant database. SQL inside that database must not +invent an additional customer discriminator column. Use only columns present in tracked +schema/semantics, and fix SQL-generation guidance rather than rewriting one unknown column +to another guessed column. +""" return f"""You are fixing failure report #{report.id} in the Laravel AI Chat repository. @@ -68,6 +77,7 @@ Observed error: Generated SQL, if relevant: {sql or '[not available]'} +{tenant_scope} Run focused tests while working. Leave the repository with the fix and regression test applied. """ diff --git a/src/lai_automation/worker.py b/src/lai_automation/worker.py index 3d2a2cd..b325436 100644 --- a/src/lai_automation/worker.py +++ b/src/lai_automation/worker.py @@ -50,6 +50,18 @@ class AutomationWorker: metadata["changes"] = asdict(summary) policy = evaluate_changes(summary, self.settings, workspace.path) if not policy.allowed: + if not summary.files and attempt < self.settings.max_agent_attempts: + LOG.warning( + "Agent produced no changes for report %s on attempt %s; requesting correction", + report.id, + attempt, + ) + task = build_retry_task( + RuntimeError( + "No file changes were applied. Make the smallest concrete code and regression-test edits now." + ) + ) + continue raise RuntimeError( "Policy rejected changes: " + "; ".join(policy.errors) ) diff --git a/tests/test_agent.py b/tests/test_agent.py index 9b54212..8802b16 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -12,7 +12,7 @@ def test_aider_command_cannot_mutate_git_configuration_or_send_analytics() -> No assert "--no-analytics" in command assert "--no-auto-commits" in command assert command.count("/dev/null") == 2 - assert command[-4:] == ["--edit-format", "diff", "--timeout", "60"] + assert command[-4:] == ["--edit-format", "diff", "--timeout", "180"] def test_aider_environment_does_not_include_database_credentials(tmp_path, monkeypatch) -> None: diff --git a/tests/test_prompt.py b/tests/test_prompt.py index f8bc3af..40f6afd 100644 --- a/tests/test_prompt.py +++ b/tests/test_prompt.py @@ -59,3 +59,26 @@ def test_retry_task_contains_failure_without_secrets() -> None: assert "Missing import" in task assert "secret-value" not in task assert "Do not remove or weaken tests" in task + + +def test_customer_database_task_explains_tenant_scope() -> None: + report = FailureReport( + id=13, + chat_message_id=1, + source="automatic", + category="sql_execution", + user_feedback=None, + feedback_type=None, + expected_behavior=None, + prompt="What did this customer buy most?", + response=None, + sql_query="SELECT * FROM invoice_row WHERE InvoiceCreator = 'tenant'", + error_message="Unknown column InvoiceCreator", + context={"database_type": "customer"}, + ) + + task = build_task(report) + + assert "already selected one tenant database" in task + assert "must not" in task + assert "guessed column" in task diff --git a/tests/test_worker.py b/tests/test_worker.py index 066bb2b..3406285 100644 --- a/tests/test_worker.py +++ b/tests/test_worker.py @@ -97,3 +97,45 @@ def test_failed_validation_gets_one_correction_before_pr() -> None: assert worker._run_tests.call_count == 2 workspace.push_branch.assert_called_once() worker.reports.complete.assert_called_once() + + +def test_no_change_response_gets_one_correction() -> None: + report = FailureReport( + id=14, + chat_message_id=1, + source="automatic", + category="sql_execution", + user_feedback=None, + feedback_type=None, + expected_behavior=None, + prompt="List products", + response=None, + sql_query=None, + error_message="Unknown column", + context={}, + ) + config = settings(mode="pr", dry_run=False, gitea_token="token") + worker = AutomationWorker(config) + worker.reports = Mock() + worker.reports.claim_next.return_value = report + worker.agent = Mock() + worker.agent.run.side_effect = ["no edit", "applied edit"] + worker.gitea = Mock() + worker._run_tests = Mock(return_value=[]) + workspace = Mock() + workspace.path = Path("/tmp/nonexistent-workspace") + workspace.branch = "ai/failure-14" + workspace.summary.side_effect = [ + ChangeSummary((), 0, 0), + ChangeSummary(("tests/Feature/FixTest.php",), 5, 0), + ChangeSummary(("tests/Feature/FixTest.php",), 5, 0), + ] + workspace.commit.return_value = "commit-id" + + with patch("lai_automation.worker.GitWorkspace", return_value=workspace): + assert worker.run_once() + + assert worker.agent.run.call_count == 2 + assert "No file changes" in worker.agent.run.call_args_list[1].args[1] + worker._run_tests.assert_called_once() + workspace.push_branch.assert_called_once()