feat: isolate agentic worktrees and surface execution evidence
This commit is contained in:
@@ -23,6 +23,7 @@ from cross_eval.models import (
|
||||
StepConfig,
|
||||
)
|
||||
from cross_eval.pipeline import (
|
||||
_assert_base_repo_isolation,
|
||||
_commit_iteration,
|
||||
_finalize_worktree,
|
||||
_has_agentic_steps,
|
||||
@@ -34,6 +35,7 @@ from cross_eval.worktree import (
|
||||
commit_worktree,
|
||||
create_worktree,
|
||||
make_branch_name,
|
||||
make_worktree_dir,
|
||||
remove_worktree,
|
||||
)
|
||||
|
||||
@@ -191,6 +193,41 @@ class TestMakeBranchName(unittest.TestCase):
|
||||
self.assertEqual(len(ts_part), 15) # YYYYMMDD_HHMMSS
|
||||
|
||||
|
||||
class TestMakeWorktreeDir(unittest.TestCase):
|
||||
"""make_worktree_dir chooses an external temp location."""
|
||||
|
||||
def test_uses_tmp_dir_outside_repo(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
base = Path(td) / "repo"
|
||||
base.mkdir()
|
||||
path = make_worktree_dir(base, "cross-eval/review-fix_20260313_123456")
|
||||
self.assertIn("cross-eval-worktrees", str(path))
|
||||
self.assertNotIn(str(base), str(path))
|
||||
|
||||
|
||||
class TestBaseRepoIsolation(unittest.TestCase):
|
||||
"""Base repo mutations should fail fast during agentic execution."""
|
||||
|
||||
def test_raises_when_base_repo_status_changes(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
base = Path(td) / "repo"
|
||||
worktree = Path(td) / "worktree"
|
||||
base.mkdir()
|
||||
worktree.mkdir()
|
||||
|
||||
with self.assertRaises(RuntimeError) as ctx:
|
||||
_assert_base_repo_isolation(
|
||||
base,
|
||||
"M cross_eval/agent.py",
|
||||
step_name="coding",
|
||||
agent_name="claude-coder",
|
||||
worktree_path=worktree,
|
||||
baseline_status="M cross_eval/agent.py",
|
||||
)
|
||||
|
||||
self.assertIn("base repository", str(ctx.exception))
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# 2. agent.py agentic tests (mocking subprocess)
|
||||
# ===================================================================
|
||||
@@ -513,6 +550,33 @@ class TestSetupWorktreeCalledForAgentic(unittest.TestCase):
|
||||
mock_setup.assert_called_once()
|
||||
|
||||
|
||||
class TestSetupWorktreeLocation(unittest.TestCase):
|
||||
"""_setup_worktree places agentic worktrees outside the base repo."""
|
||||
|
||||
def test_worktree_is_created_outside_repo(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
base = Path(td) / "repo"
|
||||
run_dir = base / ".cross-eval" / "output" / "smoke"
|
||||
base.mkdir()
|
||||
run_dir.mkdir(parents=True)
|
||||
_init_git_repo(base)
|
||||
|
||||
worktree_path, branch_name = _setup_worktree(base, run_dir, "review-fix")
|
||||
try:
|
||||
self.assertTrue(worktree_path.exists())
|
||||
self.assertNotIn(str(base.resolve()), str(worktree_path.resolve()))
|
||||
self.assertEqual(
|
||||
(run_dir / "worktree_path.txt").read_text(encoding="utf-8").strip(),
|
||||
str(worktree_path),
|
||||
)
|
||||
self.assertEqual(
|
||||
(run_dir / "worktree_branch.txt").read_text(encoding="utf-8").strip(),
|
||||
branch_name,
|
||||
)
|
||||
finally:
|
||||
remove_worktree(base, worktree_path)
|
||||
|
||||
|
||||
class TestReviewerRunsInWorktreeCwd(unittest.TestCase):
|
||||
"""Reviewer runs with worktree cwd (not original cwd) when worktree exists."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user