kin: auto-commit after pipeline

This commit is contained in:
Gros Frumos 2026-03-17 15:40:31 +02:00
parent f805aff86b
commit 6c2da26b6c
6 changed files with 138 additions and 10 deletions

View file

@ -6,6 +6,7 @@ Each agent = separate process with isolated context.
import json
import logging
import os
import shlex
import shutil
import sqlite3
import subprocess
@ -778,16 +779,20 @@ def _is_test_failure(result: dict) -> bool:
_AUTO_TEST_ROLES = {"backend_dev", "frontend_dev"}
def _run_project_tests(project_path: str, timeout: int = 120) -> dict:
"""Run `make test` in project_path. Returns {success, output, returncode}.
def _run_project_tests(project_path: str, test_command: str = 'make test', timeout: int = 120) -> dict:
"""Run test_command in project_path. Returns {success, output, returncode}.
Never raises all errors are captured and returned in output.
"""
env = _build_claude_env()
make_cmd = shutil.which("make", path=env["PATH"]) or "make"
parts = shlex.split(test_command)
if not parts:
return {"success": False, "output": "Empty test_command", "returncode": -1}
resolved = shutil.which(parts[0], path=env["PATH"]) or parts[0]
cmd = [resolved] + parts[1:]
try:
result = subprocess.run(
[make_cmd, "test"],
cmd,
cwd=project_path,
capture_output=True,
text=True,
@ -797,9 +802,9 @@ def _run_project_tests(project_path: str, timeout: int = 120) -> dict:
output = (result.stdout or "") + (result.stderr or "")
return {"success": result.returncode == 0, "output": output, "returncode": result.returncode}
except subprocess.TimeoutExpired:
return {"success": False, "output": f"make test timed out after {timeout}s", "returncode": 124}
return {"success": False, "output": f"{test_command} timed out after {timeout}s", "returncode": 124}
except FileNotFoundError:
return {"success": False, "output": "make not found — no Makefile or make not in PATH", "returncode": 127}
return {"success": False, "output": f"{parts[0]} not found in PATH", "returncode": 127}
except Exception as exc:
return {"success": False, "output": f"Test run error: {exc}", "returncode": -1}
@ -1568,14 +1573,15 @@ def run_pipeline(
):
max_auto_test_attempts = int(os.environ.get("KIN_AUTO_TEST_MAX_ATTEMPTS") or 3)
p_path_str = str(Path(project_for_wt["path"]).expanduser())
test_run = _run_project_tests(p_path_str)
p_test_cmd = project_for_wt.get("test_command") or "make test"
test_run = _run_project_tests(p_path_str, p_test_cmd)
results.append({"role": "_auto_test", "success": test_run["success"],
"output": test_run["output"], "_project_test": True})
auto_test_attempt = 0
while not test_run["success"] and auto_test_attempt < max_auto_test_attempts:
auto_test_attempt += 1
fix_context = (
f"Automated project test run (make test) failed after your changes.\n"
f"Automated project test run ({p_test_cmd}) failed after your changes.\n"
f"Test output:\n{test_run['output'][:4000]}\n"
f"Fix the failing tests. Do NOT modify test files."
)
@ -1591,13 +1597,13 @@ def run_pipeline(
total_tokens += fix_result.get("tokens_used") or 0
total_duration += fix_result.get("duration_seconds") or 0
results.append({**fix_result, "_auto_test_fix_attempt": auto_test_attempt})
test_run = _run_project_tests(p_path_str)
test_run = _run_project_tests(p_path_str, p_test_cmd)
results.append({"role": "_auto_test", "success": test_run["success"],
"output": test_run["output"], "_project_test": True,
"_attempt": auto_test_attempt})
if not test_run["success"]:
block_reason = (
f"Auto-test (make test) failed after {auto_test_attempt} fix attempt(s). "
f"Auto-test ({p_test_cmd}) failed after {auto_test_attempt} fix attempt(s). "
f"Last output: {test_run['output'][:500]}"
)
models.update_task(conn, task_id, status="blocked", blocked_reason=block_reason)