kin: KIN-045 добавить в GUI третью кнопку Revise (🔄) рядом с Approve/Reject. Revise = вернуть задачу агенту с комментарием человека. Модалка с textarea 'что доисследовать/доработать'. Задача возвращается в in_progress, агент получает свой предыдущий output + комментарий директора и дорабатывает

This commit is contained in:
Gros Frumos 2026-03-16 07:21:36 +02:00
parent 4fd825dc58
commit 01c39cc45c
3 changed files with 147 additions and 0 deletions

View file

@ -168,6 +168,19 @@ def test_revise_not_found(client):
assert r.status_code == 404 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): def test_task_pipeline_not_found(client):
r = client.get("/api/tasks/NOPE/pipeline") r = client.get("/api/tasks/NOPE/pipeline")
assert r.status_code == 404 assert r.status_code == 404

View file

@ -161,3 +161,107 @@ class TestLanguageInProject:
def test_context_carries_language(self, conn): def test_context_carries_language(self, conn):
ctx = build_context(conn, "VDOL-001", "pm", "vdol") ctx = build_context(conn, "VDOL-001", "pm", "vdol")
assert ctx["project"]["language"] == "ru" 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

View file

@ -1412,6 +1412,36 @@ class TestCompletionMode:
"KIN-063: project-level 'review' должен применяться когда задача не имеет override" "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= # KIN-048: _run_autocommit — флаг, git path, env=