From 01c39cc45c3949bbf0d8ae0c44c66fb37cd20b99 Mon Sep 17 00:00:00 2001 From: Gros Frumos Date: Mon, 16 Mar 2026 07:21:36 +0200 Subject: [PATCH] =?UTF-8?q?kin:=20KIN-045=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=B2=20GUI=20=D1=82=D1=80=D0=B5=D1=82?= =?UTF-8?q?=D1=8C=D1=8E=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D1=83=20Revise=20?= =?UTF-8?q?(=F0=9F=94=84)=20=D1=80=D1=8F=D0=B4=D0=BE=D0=BC=20=D1=81=20Appr?= =?UTF-8?q?ove/Reject.=20Revise=20=3D=20=D0=B2=D0=B5=D1=80=D0=BD=D1=83?= =?UTF-8?q?=D1=82=D1=8C=20=D0=B7=D0=B0=D0=B4=D0=B0=D1=87=D1=83=20=D0=B0?= =?UTF-8?q?=D0=B3=D0=B5=D0=BD=D1=82=D1=83=20=D1=81=20=D0=BA=D0=BE=D0=BC?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D0=B5=D0=BC=20=D1=87?= =?UTF-8?q?=D0=B5=D0=BB=D0=BE=D0=B2=D0=B5=D0=BA=D0=B0.=20=D0=9C=D0=BE?= =?UTF-8?q?=D0=B4=D0=B0=D0=BB=D0=BA=D0=B0=20=D1=81=20textarea=20'=D1=87?= =?UTF-8?q?=D1=82=D0=BE=20=D0=B4=D0=BE=D0=B8=D1=81=D1=81=D0=BB=D0=B5=D0=B4?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=82=D1=8C/=D0=B4=D0=BE=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=B0=D1=82=D1=8C'.=20=D0=97=D0=B0=D0=B4=D0=B0?= =?UTF-8?q?=D1=87=D0=B0=20=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0=D1=89=D0=B0?= =?UTF-8?q?=D0=B5=D1=82=D1=81=D1=8F=20=D0=B2=20in=5Fprogress,=20=D0=B0?= =?UTF-8?q?=D0=B3=D0=B5=D0=BD=D1=82=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B0?= =?UTF-8?q?=D0=B5=D1=82=20=D1=81=D0=B2=D0=BE=D0=B9=20=D0=BF=D1=80=D0=B5?= =?UTF-8?q?=D0=B4=D1=8B=D0=B4=D1=83=D1=89=D0=B8=D0=B9=20output=20+=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8?= =?UTF-8?q?=D0=B9=20=D0=B4=D0=B8=D1=80=D0=B5=D0=BA=D1=82=D0=BE=D1=80=D0=B0?= =?UTF-8?q?=20=D0=B8=20=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=B0=D1=82=D1=8B?= =?UTF-8?q?=D0=B2=D0=B0=D0=B5=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_api.py | 13 +++++ tests/test_context_builder.py | 104 ++++++++++++++++++++++++++++++++++ tests/test_runner.py | 30 ++++++++++ 3 files changed, 147 insertions(+) diff --git a/tests/test_api.py b/tests/test_api.py index 75c87fc..3de2c47 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -168,6 +168,19 @@ def test_revise_not_found(client): assert r.status_code == 404 +def test_revise_task_response_includes_comment(client): + """Ответ /revise содержит поле comment с переданным текстом.""" + r = client.post("/api/tasks/P1-001/revise", json={"comment": "Уточни требования"}) + assert r.status_code == 200 + assert r.json()["comment"] == "Уточни требования" + + +def test_revise_task_missing_comment_returns_422(client): + """Запрос /revise без поля comment → 422 Unprocessable Entity (Pydantic validation).""" + r = client.post("/api/tasks/P1-001/revise", json={}) + assert r.status_code == 422 + + def test_task_pipeline_not_found(client): r = client.get("/api/tasks/NOPE/pipeline") assert r.status_code == 404 diff --git a/tests/test_context_builder.py b/tests/test_context_builder.py index 64bf732..9b78a25 100644 --- a/tests/test_context_builder.py +++ b/tests/test_context_builder.py @@ -161,3 +161,107 @@ class TestLanguageInProject: def test_context_carries_language(self, conn): ctx = build_context(conn, "VDOL-001", "pm", "vdol") assert ctx["project"]["language"] == "ru" + + +# --------------------------------------------------------------------------- +# KIN-045: Revise context — revise_comment + last agent output injection +# --------------------------------------------------------------------------- + +class TestReviseContext: + """build_context и format_prompt корректно инжектируют контекст ревизии.""" + + def test_build_context_includes_revise_comment_in_task(self, conn): + """Если у задачи есть revise_comment, он попадает в ctx['task'].""" + conn.execute( + "UPDATE tasks SET revise_comment=? WHERE id='VDOL-001'", + ("Доисследуй edge case с пустым массивом",), + ) + conn.commit() + ctx = build_context(conn, "VDOL-001", "backend_dev", "vdol") + assert ctx["task"]["revise_comment"] == "Доисследуй edge case с пустым массивом" + + def test_build_context_fetches_last_agent_output_when_revise_comment_set(self, conn): + """При revise_comment build_context достаёт last_agent_output из agent_logs.""" + from core import models + models.log_agent_run( + conn, "vdol", "developer", "execute", + task_id="VDOL-001", + output_summary="Реализован endpoint POST /api/items", + success=True, + ) + conn.execute( + "UPDATE tasks SET revise_comment=? WHERE id='VDOL-001'", + ("Добавь валидацию входных данных",), + ) + conn.commit() + ctx = build_context(conn, "VDOL-001", "backend_dev", "vdol") + assert ctx.get("last_agent_output") == "Реализован endpoint POST /api/items" + + def test_build_context_no_last_agent_output_when_no_successful_logs(self, conn): + """revise_comment есть, но нет успешных логов — last_agent_output отсутствует.""" + from core import models + models.log_agent_run( + conn, "vdol", "developer", "execute", + task_id="VDOL-001", + output_summary="Permission denied", + success=False, + ) + conn.execute( + "UPDATE tasks SET revise_comment=? WHERE id='VDOL-001'", + ("Повтори без ошибок",), + ) + conn.commit() + ctx = build_context(conn, "VDOL-001", "backend_dev", "vdol") + assert "last_agent_output" not in ctx + + def test_build_context_no_revise_fields_when_no_revise_comment(self, conn): + """Обычная задача без revise_comment не получает last_agent_output в контексте.""" + from core import models + models.log_agent_run( + conn, "vdol", "developer", "execute", + task_id="VDOL-001", + output_summary="Всё готово", + success=True, + ) + # revise_comment не устанавливаем + ctx = build_context(conn, "VDOL-001", "backend_dev", "vdol") + assert "last_agent_output" not in ctx + assert ctx["task"].get("revise_comment") is None + + def test_format_prompt_includes_director_revision_request(self, conn): + """format_prompt содержит секцию '## Director's revision request:' при revise_comment.""" + conn.execute( + "UPDATE tasks SET revise_comment=? WHERE id='VDOL-001'", + ("Обработай случай пустого списка",), + ) + conn.commit() + ctx = build_context(conn, "VDOL-001", "backend_dev", "vdol") + prompt = format_prompt(ctx, "backend_dev", "You are a developer.") + assert "## Director's revision request:" in prompt + assert "Обработай случай пустого списка" in prompt + + def test_format_prompt_includes_previous_output_before_revision(self, conn): + """format_prompt содержит '## Your previous output (before revision):' при last_agent_output.""" + from core import models + models.log_agent_run( + conn, "vdol", "developer", "execute", + task_id="VDOL-001", + output_summary="Сделал миграцию БД", + success=True, + ) + conn.execute( + "UPDATE tasks SET revise_comment=? WHERE id='VDOL-001'", + ("Ещё добавь индекс",), + ) + conn.commit() + ctx = build_context(conn, "VDOL-001", "backend_dev", "vdol") + prompt = format_prompt(ctx, "backend_dev", "You are a developer.") + assert "## Your previous output (before revision):" in prompt + assert "Сделал миграцию БД" in prompt + + def test_format_prompt_no_revision_sections_when_no_revise_comment(self, conn): + """Без revise_comment в prompt нет секций ревизии.""" + ctx = build_context(conn, "VDOL-001", "backend_dev", "vdol") + prompt = format_prompt(ctx, "backend_dev", "You are a developer.") + assert "## Director's revision request:" not in prompt + assert "## Your previous output (before revision):" not in prompt diff --git a/tests/test_runner.py b/tests/test_runner.py index d79746b..61fce2b 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -1412,6 +1412,36 @@ class TestCompletionMode: "KIN-063: project-level 'review' должен применяться когда задача не имеет override" ) + @patch("core.followup.generate_followups") + @patch("agents.runner.run_hooks") + @patch("agents.runner.subprocess.run") + def test_auto_complete_not_broken_by_revise_comment(self, mock_run, mock_hooks, mock_followup, conn): + """Регрессия KIN-045: revise_comment в задаче не ломает auto_complete flow. + + Задача прошла ревизию (revise_comment != None, status=in_progress), + затем повторно запускается пайплайн в auto_complete режиме. + Последний шаг — tester → задача должна получить status='done'. + """ + mock_run.return_value = _mock_claude_success({"result": "all tests pass"}) + mock_hooks.return_value = [] + mock_followup.return_value = {"created": [], "pending_actions": []} + + models.update_project(conn, "vdol", execution_mode="auto_complete") + models.update_task( + conn, "VDOL-001", + status="in_progress", + revise_comment="Добавь тест для пустого массива", + ) + + steps = [{"role": "developer", "brief": "fix"}, {"role": "tester", "brief": "test"}] + result = run_pipeline(conn, "VDOL-001", steps) + + assert result["success"] is True + task = models.get_task(conn, "VDOL-001") + assert task["status"] == "done", ( + "KIN-045: revise_comment не должен мешать auto_complete авто-завершению" + ) + # --------------------------------------------------------------------------- # KIN-048: _run_autocommit — флаг, git path, env=