kin: KIN-080 Разобраться с KIN-FIX-003 и KIN-FIX-004, одна из задач уже выполнена, вторая берется в работу (руками завершаю) но в задаче не меняется текущий статус
This commit is contained in:
parent
bfc8f1c0bb
commit
c67fa379b3
2 changed files with 125 additions and 5 deletions
|
|
@ -76,7 +76,7 @@ class ClaudeAuthError(Exception):
|
|||
def check_claude_auth(timeout: int = 10) -> None:
|
||||
"""Check that claude CLI is authenticated before running a pipeline.
|
||||
|
||||
Runs: claude -p 'ok' --output-format json --no-verbose with timeout.
|
||||
Runs: claude -p 'ok' --output-format json with timeout.
|
||||
Returns None if auth is confirmed.
|
||||
Raises ClaudeAuthError if:
|
||||
- claude CLI not found in PATH (FileNotFoundError)
|
||||
|
|
@ -89,7 +89,7 @@ def check_claude_auth(timeout: int = 10) -> None:
|
|||
env = _build_claude_env()
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[claude_cmd, "-p", "ok", "--output-format", "json", "--no-verbose"],
|
||||
[claude_cmd, "-p", "ok", "--output-format", "json"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout,
|
||||
|
|
@ -1084,7 +1084,13 @@ def run_pipeline(
|
|||
last_role = steps[-1].get("role", "") if steps else ""
|
||||
auto_eligible = last_role in {"tester", "reviewer"}
|
||||
|
||||
if mode == "auto_complete" and auto_eligible:
|
||||
# Guard: re-fetch current status — user may have manually changed it while pipeline ran
|
||||
current_task = models.get_task(conn, task_id)
|
||||
current_status = current_task.get("status") if current_task else None
|
||||
|
||||
if current_status in ("done", "cancelled"):
|
||||
pass # User finished manually — don't overwrite
|
||||
elif mode == "auto_complete" and auto_eligible:
|
||||
# Auto-complete mode: last step is tester/reviewer — skip review, approve immediately
|
||||
models.update_task(conn, task_id, status="done")
|
||||
try:
|
||||
|
|
@ -1114,8 +1120,8 @@ def run_pipeline(
|
|||
except Exception:
|
||||
pass
|
||||
else:
|
||||
# Review mode: wait for manual approval
|
||||
models.update_task(conn, task_id, status="review", execution_mode="review")
|
||||
# Review mode: wait for manual approval (don't overwrite execution_mode)
|
||||
models.update_task(conn, task_id, status="review")
|
||||
|
||||
# Run post-pipeline hooks (failures don't affect pipeline status)
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -394,6 +394,120 @@ class TestAutoMode:
|
|||
mock_resolve.assert_called_once_with(conn, "VDOL-001", pending)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# KIN-080: Guard — не перезаписывать статус, если пользователь изменил вручную
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestPipelineStatusGuard:
|
||||
"""Тесты guard-check: pipeline не должен перезаписывать статус задачи,
|
||||
если пользователь вручную изменил его на 'done' или 'cancelled' пока
|
||||
pipeline выполнялся."""
|
||||
|
||||
@patch("agents.runner._run_autocommit")
|
||||
@patch("agents.runner._run_learning_extraction")
|
||||
@patch("agents.runner._get_changed_files")
|
||||
@patch("agents.runner.run_hooks")
|
||||
@patch("agents.runner.subprocess.run")
|
||||
def test_pipeline_preserves_done_status_set_during_execution(
|
||||
self, mock_run, mock_hooks, mock_get_files, mock_learn, mock_autocommit, conn
|
||||
):
|
||||
"""Guard: если пользователь вручную поставил 'done' пока pipeline работал —
|
||||
итоговый статус должен остаться 'done', а не перезаписаться в 'review'."""
|
||||
def side_effect(*args, **kwargs):
|
||||
# Имитируем ручную смену статуса во время выполнения агента
|
||||
models.update_task(conn, "VDOL-001", status="done")
|
||||
return _mock_claude_success({"result": "done"})
|
||||
|
||||
mock_run.side_effect = side_effect
|
||||
mock_hooks.return_value = []
|
||||
mock_get_files.return_value = []
|
||||
mock_learn.return_value = {"added": 0, "skipped": 0}
|
||||
|
||||
steps = [{"role": "debugger", "brief": "find"}]
|
||||
result = run_pipeline(conn, "VDOL-001", steps)
|
||||
|
||||
assert result["success"] is True
|
||||
task = models.get_task(conn, "VDOL-001")
|
||||
assert task["status"] == "done" # guard НЕ перезаписал в "review"
|
||||
|
||||
@patch("agents.runner._run_autocommit")
|
||||
@patch("agents.runner._run_learning_extraction")
|
||||
@patch("agents.runner._get_changed_files")
|
||||
@patch("agents.runner.run_hooks")
|
||||
@patch("agents.runner.subprocess.run")
|
||||
def test_pipeline_preserves_cancelled_status_set_during_execution(
|
||||
self, mock_run, mock_hooks, mock_get_files, mock_learn, mock_autocommit, conn
|
||||
):
|
||||
"""Guard: если пользователь вручную поставил 'cancelled' пока pipeline работал —
|
||||
итоговый статус должен остаться 'cancelled'."""
|
||||
def side_effect(*args, **kwargs):
|
||||
models.update_task(conn, "VDOL-001", status="cancelled")
|
||||
return _mock_claude_success({"result": "done"})
|
||||
|
||||
mock_run.side_effect = side_effect
|
||||
mock_hooks.return_value = []
|
||||
mock_get_files.return_value = []
|
||||
mock_learn.return_value = {"added": 0, "skipped": 0}
|
||||
|
||||
steps = [{"role": "debugger", "brief": "find"}]
|
||||
result = run_pipeline(conn, "VDOL-001", steps)
|
||||
|
||||
assert result["success"] is True
|
||||
task = models.get_task(conn, "VDOL-001")
|
||||
assert task["status"] == "cancelled" # guard НЕ перезаписал в "review"
|
||||
|
||||
@patch("agents.runner._run_autocommit")
|
||||
@patch("agents.runner._run_learning_extraction")
|
||||
@patch("agents.runner._get_changed_files")
|
||||
@patch("agents.runner.run_hooks")
|
||||
@patch("agents.runner.subprocess.run")
|
||||
def test_pipeline_sets_review_when_no_manual_override(
|
||||
self, mock_run, mock_hooks, mock_get_files, mock_learn, mock_autocommit, conn
|
||||
):
|
||||
"""Нормальный случай: задача в in_progress, пользователь не трогал статус —
|
||||
после pipeline устанавливается 'review'."""
|
||||
mock_run.return_value = _mock_claude_success({"result": "done"})
|
||||
mock_hooks.return_value = []
|
||||
mock_get_files.return_value = []
|
||||
mock_learn.return_value = {"added": 0, "skipped": 0}
|
||||
|
||||
steps = [{"role": "debugger", "brief": "find"}]
|
||||
result = run_pipeline(conn, "VDOL-001", steps)
|
||||
|
||||
assert result["success"] is True
|
||||
task = models.get_task(conn, "VDOL-001")
|
||||
assert task["status"] == "review"
|
||||
|
||||
@patch("agents.runner._run_autocommit")
|
||||
@patch("agents.runner._run_learning_extraction")
|
||||
@patch("agents.runner._get_changed_files")
|
||||
@patch("core.followup.generate_followups")
|
||||
@patch("agents.runner.run_hooks")
|
||||
@patch("agents.runner.subprocess.run")
|
||||
def test_auto_mode_preserves_done_status_set_during_execution(
|
||||
self, mock_run, mock_hooks, mock_followup, mock_get_files, mock_learn, mock_autocommit, conn
|
||||
):
|
||||
"""Guard в auto_complete mode: если пользователь вручную поставил 'done'
|
||||
пока pipeline работал — guard пропускает обновление (уже done)."""
|
||||
def side_effect(*args, **kwargs):
|
||||
models.update_task(conn, "VDOL-001", status="done")
|
||||
return _mock_claude_success({"result": "done"})
|
||||
|
||||
mock_run.side_effect = side_effect
|
||||
mock_hooks.return_value = []
|
||||
mock_followup.return_value = {"created": [], "pending_actions": []}
|
||||
mock_get_files.return_value = []
|
||||
mock_learn.return_value = {"added": 0, "skipped": 0}
|
||||
|
||||
models.update_project(conn, "vdol", execution_mode="auto_complete")
|
||||
steps = [{"role": "tester", "brief": "verify"}]
|
||||
result = run_pipeline(conn, "VDOL-001", steps)
|
||||
|
||||
assert result["success"] is True
|
||||
task = models.get_task(conn, "VDOL-001")
|
||||
assert task["status"] == "done"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Retry on permission error
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue