kin: KIN-048 Post-pipeline hook: автокоммит после успешного завершения задачи. git add -A && git commit -m 'kin: TASK_ID TITLE'. Срабатывает автоматически как rebuild-frontend.
This commit is contained in:
parent
8a6f280cbd
commit
ae21e48b65
13 changed files with 1554 additions and 65 deletions
|
|
@ -1,7 +1,8 @@
|
|||
"""
|
||||
Tests for KIN-012 auto mode features:
|
||||
Tests for KIN-012/KIN-063 auto mode features:
|
||||
|
||||
- TestAutoApprove: pipeline auto-approves (status → done) без ручного review
|
||||
(KIN-063: auto_complete только если последний шаг — tester или reviewer)
|
||||
- TestAutoRerunOnPermissionDenied: runner делает retry при permission error,
|
||||
останавливается после одного retry (лимит = 1)
|
||||
- TestAutoFollowup: generate_followups вызывается сразу, без ожидания
|
||||
|
|
@ -75,30 +76,30 @@ class TestAutoApprove:
|
|||
@patch("agents.runner.run_hooks")
|
||||
@patch("agents.runner.subprocess.run")
|
||||
def test_auto_mode_sets_status_done(self, mock_run, mock_hooks, mock_followup, conn):
|
||||
"""Auto-режим: статус задачи становится 'done', а не 'review'."""
|
||||
"""Auto-complete режим: статус становится 'done', если последний шаг — tester."""
|
||||
mock_run.return_value = _mock_success()
|
||||
mock_hooks.return_value = []
|
||||
mock_followup.return_value = {"created": [], "pending_actions": []}
|
||||
|
||||
models.update_project(conn, "vdol", execution_mode="auto")
|
||||
steps = [{"role": "debugger", "brief": "find bug"}]
|
||||
models.update_project(conn, "vdol", execution_mode="auto_complete")
|
||||
steps = [{"role": "debugger", "brief": "find bug"}, {"role": "tester", "brief": "verify fix"}]
|
||||
result = run_pipeline(conn, "VDOL-001", steps)
|
||||
|
||||
assert result["success"] is True
|
||||
task = models.get_task(conn, "VDOL-001")
|
||||
assert task["status"] == "done", "Auto-mode должен auto-approve: status=done"
|
||||
assert task["status"] == "done", "Auto-complete должен auto-approve: status=done"
|
||||
|
||||
@patch("core.followup.generate_followups")
|
||||
@patch("agents.runner.run_hooks")
|
||||
@patch("agents.runner.subprocess.run")
|
||||
def test_auto_mode_fires_task_auto_approved_hook(self, mock_run, mock_hooks, mock_followup, conn):
|
||||
"""В auto-режиме срабатывает хук task_auto_approved."""
|
||||
"""В auto_complete-режиме срабатывает хук task_auto_approved (если последний шаг — tester)."""
|
||||
mock_run.return_value = _mock_success()
|
||||
mock_hooks.return_value = []
|
||||
mock_followup.return_value = {"created": [], "pending_actions": []}
|
||||
|
||||
models.update_project(conn, "vdol", execution_mode="auto")
|
||||
steps = [{"role": "debugger", "brief": "find bug"}]
|
||||
models.update_project(conn, "vdol", execution_mode="auto_complete")
|
||||
steps = [{"role": "debugger", "brief": "find bug"}, {"role": "tester", "brief": "verify"}]
|
||||
run_pipeline(conn, "VDOL-001", steps)
|
||||
|
||||
events = _get_hook_events(mock_hooks)
|
||||
|
|
@ -140,20 +141,20 @@ class TestAutoApprove:
|
|||
@patch("agents.runner.run_hooks")
|
||||
@patch("agents.runner.subprocess.run")
|
||||
def test_task_level_auto_overrides_project_review(self, mock_run, mock_hooks, mock_followup, conn):
|
||||
"""Если у задачи execution_mode=auto, pipeline auto-approve, даже если проект в review."""
|
||||
"""Если у задачи execution_mode=auto_complete, pipeline auto-approve, даже если проект в review."""
|
||||
mock_run.return_value = _mock_success()
|
||||
mock_hooks.return_value = []
|
||||
mock_followup.return_value = {"created": [], "pending_actions": []}
|
||||
|
||||
# Проект в review, но задача — auto
|
||||
models.update_task(conn, "VDOL-001", execution_mode="auto")
|
||||
# Проект в review, но задача — auto_complete
|
||||
models.update_task(conn, "VDOL-001", execution_mode="auto_complete")
|
||||
|
||||
steps = [{"role": "debugger", "brief": "find"}]
|
||||
steps = [{"role": "debugger", "brief": "find"}, {"role": "reviewer", "brief": "approve"}]
|
||||
result = run_pipeline(conn, "VDOL-001", steps)
|
||||
|
||||
assert result["success"] is True
|
||||
task = models.get_task(conn, "VDOL-001")
|
||||
assert task["status"] == "done", "Task-level auto должен override project review"
|
||||
assert task["status"] == "done", "Task-level auto_complete должен override project review"
|
||||
|
||||
@patch("core.followup.generate_followups")
|
||||
@patch("agents.runner.run_hooks")
|
||||
|
|
@ -164,11 +165,11 @@ class TestAutoApprove:
|
|||
mock_hooks.return_value = []
|
||||
mock_followup.return_value = {"created": [], "pending_actions": []}
|
||||
|
||||
models.update_project(conn, "vdol", execution_mode="auto")
|
||||
steps = [{"role": "debugger", "brief": "find"}]
|
||||
models.update_project(conn, "vdol", execution_mode="auto_complete")
|
||||
steps = [{"role": "debugger", "brief": "find"}, {"role": "tester", "brief": "test"}]
|
||||
result = run_pipeline(conn, "VDOL-001", steps)
|
||||
|
||||
assert result.get("mode") == "auto"
|
||||
assert result.get("mode") == "auto_complete"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -178,10 +179,12 @@ class TestAutoApprove:
|
|||
class TestAutoRerunOnPermissionDenied:
|
||||
"""Runner повторяет шаг при permission issues, останавливается по лимиту (1 retry)."""
|
||||
|
||||
@patch("agents.runner._run_autocommit")
|
||||
@patch("agents.runner._run_learning_extraction")
|
||||
@patch("core.followup.generate_followups")
|
||||
@patch("agents.runner.run_hooks")
|
||||
@patch("agents.runner.subprocess.run")
|
||||
def test_auto_mode_retries_on_permission_error(self, mock_run, mock_hooks, mock_followup, conn):
|
||||
def test_auto_mode_retries_on_permission_error(self, mock_run, mock_hooks, mock_followup, mock_learn, mock_autocommit, conn):
|
||||
"""Auto-режим: при permission denied runner делает 1 retry с allow_write=True."""
|
||||
mock_run.side_effect = [
|
||||
_mock_permission_denied(), # 1-й вызов: permission error
|
||||
|
|
@ -189,8 +192,9 @@ class TestAutoRerunOnPermissionDenied:
|
|||
]
|
||||
mock_hooks.return_value = []
|
||||
mock_followup.return_value = {"created": [], "pending_actions": []}
|
||||
mock_learn.return_value = {"added": 0, "skipped": 0}
|
||||
|
||||
models.update_project(conn, "vdol", execution_mode="auto")
|
||||
models.update_project(conn, "vdol", execution_mode="auto_complete")
|
||||
steps = [{"role": "debugger", "brief": "fix file"}]
|
||||
result = run_pipeline(conn, "VDOL-001", steps)
|
||||
|
||||
|
|
@ -209,7 +213,7 @@ class TestAutoRerunOnPermissionDenied:
|
|||
mock_hooks.return_value = []
|
||||
mock_followup.return_value = {"created": [], "pending_actions": []}
|
||||
|
||||
models.update_project(conn, "vdol", execution_mode="auto")
|
||||
models.update_project(conn, "vdol", execution_mode="auto_complete")
|
||||
steps = [{"role": "debugger", "brief": "fix"}]
|
||||
run_pipeline(conn, "VDOL-001", steps)
|
||||
|
||||
|
|
@ -229,7 +233,7 @@ class TestAutoRerunOnPermissionDenied:
|
|||
mock_hooks.return_value = []
|
||||
mock_followup.return_value = {"created": [], "pending_actions": []}
|
||||
|
||||
models.update_project(conn, "vdol", execution_mode="auto")
|
||||
models.update_project(conn, "vdol", execution_mode="auto_complete")
|
||||
steps = [{"role": "debugger", "brief": "fix"}]
|
||||
run_pipeline(conn, "VDOL-001", steps)
|
||||
|
||||
|
|
@ -248,7 +252,7 @@ class TestAutoRerunOnPermissionDenied:
|
|||
mock_hooks.return_value = []
|
||||
mock_followup.return_value = {"created": [], "pending_actions": []}
|
||||
|
||||
models.update_project(conn, "vdol", execution_mode="auto")
|
||||
models.update_project(conn, "vdol", execution_mode="auto_complete")
|
||||
steps = [{"role": "debugger", "brief": "fix"}]
|
||||
result = run_pipeline(conn, "VDOL-001", steps)
|
||||
|
||||
|
|
@ -257,10 +261,12 @@ class TestAutoRerunOnPermissionDenied:
|
|||
task = models.get_task(conn, "VDOL-001")
|
||||
assert task["status"] == "blocked"
|
||||
|
||||
@patch("agents.runner._run_autocommit")
|
||||
@patch("agents.runner._run_learning_extraction")
|
||||
@patch("core.followup.generate_followups")
|
||||
@patch("agents.runner.run_hooks")
|
||||
@patch("agents.runner.subprocess.run")
|
||||
def test_subsequent_steps_use_allow_write_after_retry(self, mock_run, mock_hooks, mock_followup, conn):
|
||||
def test_subsequent_steps_use_allow_write_after_retry(self, mock_run, mock_hooks, mock_followup, mock_learn, mock_autocommit, conn):
|
||||
"""После успешного retry все следующие шаги тоже используют allow_write."""
|
||||
mock_run.side_effect = [
|
||||
_mock_permission_denied(), # Шаг 1: permission error
|
||||
|
|
@ -269,8 +275,9 @@ class TestAutoRerunOnPermissionDenied:
|
|||
]
|
||||
mock_hooks.return_value = []
|
||||
mock_followup.return_value = {"created": [], "pending_actions": []}
|
||||
mock_learn.return_value = {"added": 0, "skipped": 0}
|
||||
|
||||
models.update_project(conn, "vdol", execution_mode="auto")
|
||||
models.update_project(conn, "vdol", execution_mode="auto_complete")
|
||||
steps = [
|
||||
{"role": "debugger", "brief": "fix"},
|
||||
{"role": "tester", "brief": "test"},
|
||||
|
|
@ -293,7 +300,7 @@ class TestAutoRerunOnPermissionDenied:
|
|||
mock_hooks.return_value = []
|
||||
mock_followup.return_value = {"created": [], "pending_actions": []}
|
||||
|
||||
models.update_project(conn, "vdol", execution_mode="auto")
|
||||
models.update_project(conn, "vdol", execution_mode="auto_complete")
|
||||
steps = [{"role": "debugger", "brief": "fix"}]
|
||||
result = run_pipeline(conn, "VDOL-001", steps)
|
||||
|
||||
|
|
@ -330,13 +337,13 @@ class TestAutoFollowup:
|
|||
@patch("agents.runner.run_hooks")
|
||||
@patch("agents.runner.subprocess.run")
|
||||
def test_auto_followup_triggered_immediately(self, mock_run, mock_hooks, mock_followup, conn):
|
||||
"""В auto-режиме generate_followups вызывается сразу после pipeline."""
|
||||
"""В auto_complete-режиме generate_followups вызывается сразу после pipeline (последний шаг — tester)."""
|
||||
mock_run.return_value = _mock_success()
|
||||
mock_hooks.return_value = []
|
||||
mock_followup.return_value = {"created": [], "pending_actions": []}
|
||||
|
||||
models.update_project(conn, "vdol", execution_mode="auto")
|
||||
steps = [{"role": "debugger", "brief": "find"}]
|
||||
models.update_project(conn, "vdol", execution_mode="auto_complete")
|
||||
steps = [{"role": "debugger", "brief": "find"}, {"role": "tester", "brief": "test"}]
|
||||
result = run_pipeline(conn, "VDOL-001", steps)
|
||||
|
||||
assert result["success"] is True
|
||||
|
|
@ -357,8 +364,8 @@ class TestAutoFollowup:
|
|||
mock_followup.return_value = {"created": [], "pending_actions": pending}
|
||||
mock_resolve.return_value = [{"resolved": "rerun", "result": {}}]
|
||||
|
||||
models.update_project(conn, "vdol", execution_mode="auto")
|
||||
steps = [{"role": "debugger", "brief": "find"}]
|
||||
models.update_project(conn, "vdol", execution_mode="auto_complete")
|
||||
steps = [{"role": "debugger", "brief": "find"}, {"role": "tester", "brief": "test"}]
|
||||
run_pipeline(conn, "VDOL-001", steps)
|
||||
|
||||
mock_resolve.assert_called_once_with(conn, "VDOL-001", pending)
|
||||
|
|
@ -392,10 +399,10 @@ class TestAutoFollowup:
|
|||
mock_hooks.return_value = []
|
||||
mock_followup.return_value = {"created": [], "pending_actions": []}
|
||||
|
||||
models.update_project(conn, "vdol", execution_mode="auto")
|
||||
models.update_project(conn, "vdol", execution_mode="auto_complete")
|
||||
models.update_task(conn, "VDOL-001", brief={"source": "followup:VDOL-000"})
|
||||
|
||||
steps = [{"role": "debugger", "brief": "find"}]
|
||||
steps = [{"role": "debugger", "brief": "find"}, {"role": "tester", "brief": "test"}]
|
||||
result = run_pipeline(conn, "VDOL-001", steps)
|
||||
|
||||
assert result["success"] is True
|
||||
|
|
@ -412,8 +419,8 @@ class TestAutoFollowup:
|
|||
mock_hooks.return_value = []
|
||||
mock_followup.side_effect = Exception("followup PM crashed")
|
||||
|
||||
models.update_project(conn, "vdol", execution_mode="auto")
|
||||
steps = [{"role": "debugger", "brief": "find"}]
|
||||
models.update_project(conn, "vdol", execution_mode="auto_complete")
|
||||
steps = [{"role": "debugger", "brief": "find"}, {"role": "tester", "brief": "test"}]
|
||||
result = run_pipeline(conn, "VDOL-001", steps)
|
||||
|
||||
assert result["success"] is True # Pipeline succeeded, followup failure absorbed
|
||||
|
|
@ -431,8 +438,8 @@ class TestAutoFollowup:
|
|||
mock_followup.return_value = {"created": [], "pending_actions": []}
|
||||
mock_resolve.return_value = []
|
||||
|
||||
models.update_project(conn, "vdol", execution_mode="auto")
|
||||
steps = [{"role": "debugger", "brief": "find"}]
|
||||
models.update_project(conn, "vdol", execution_mode="auto_complete")
|
||||
steps = [{"role": "debugger", "brief": "find"}, {"role": "tester", "brief": "test"}]
|
||||
run_pipeline(conn, "VDOL-001", steps)
|
||||
|
||||
mock_resolve.assert_not_called()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue