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:
|
def check_claude_auth(timeout: int = 10) -> None:
|
||||||
"""Check that claude CLI is authenticated before running a pipeline.
|
"""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.
|
Returns None if auth is confirmed.
|
||||||
Raises ClaudeAuthError if:
|
Raises ClaudeAuthError if:
|
||||||
- claude CLI not found in PATH (FileNotFoundError)
|
- claude CLI not found in PATH (FileNotFoundError)
|
||||||
|
|
@ -89,7 +89,7 @@ def check_claude_auth(timeout: int = 10) -> None:
|
||||||
env = _build_claude_env()
|
env = _build_claude_env()
|
||||||
try:
|
try:
|
||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
[claude_cmd, "-p", "ok", "--output-format", "json", "--no-verbose"],
|
[claude_cmd, "-p", "ok", "--output-format", "json"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
|
|
@ -1084,7 +1084,13 @@ def run_pipeline(
|
||||||
last_role = steps[-1].get("role", "") if steps else ""
|
last_role = steps[-1].get("role", "") if steps else ""
|
||||||
auto_eligible = last_role in {"tester", "reviewer"}
|
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
|
# Auto-complete mode: last step is tester/reviewer — skip review, approve immediately
|
||||||
models.update_task(conn, task_id, status="done")
|
models.update_task(conn, task_id, status="done")
|
||||||
try:
|
try:
|
||||||
|
|
@ -1114,8 +1120,8 @@ def run_pipeline(
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
# Review mode: wait for manual approval
|
# Review mode: wait for manual approval (don't overwrite execution_mode)
|
||||||
models.update_task(conn, task_id, status="review", execution_mode="review")
|
models.update_task(conn, task_id, status="review")
|
||||||
|
|
||||||
# Run post-pipeline hooks (failures don't affect pipeline status)
|
# Run post-pipeline hooks (failures don't affect pipeline status)
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -394,6 +394,120 @@ class TestAutoMode:
|
||||||
mock_resolve.assert_called_once_with(conn, "VDOL-001", pending)
|
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
|
# Retry on permission error
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue