agentic-dev-loop 1.0.0a6__tar.gz → 1.0.0a7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/PKG-INFO +1 -1
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/pyproject.toml +1 -1
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/isolation.py +80 -2
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop.egg-info/PKG-INFO +1 -1
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_isolation.py +89 -4
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_isolation_integration.py +35 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/LICENSE +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/README.md +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/setup.cfg +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/core/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/core/config.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/core/project.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/core/projection.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/core/results.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/core/verdict.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/core/workflow.js.template +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/actions.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/agents.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/cli.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/commit_shim.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/dryrun.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/graph.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/llm.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/loaders.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/outputs.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/proc.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/registry.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/stages.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/py.typed +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/viz/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/viz/__main__.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/viz/ingest.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/viz/server.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/workflow/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/workflow/cli.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop.egg-info/SOURCES.txt +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_actions.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_agents.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_cli.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_config.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_dry_run.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_e2e_fixtures.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_graph.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_isolation_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_llm.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_loaders.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_outputs.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_outputs_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_proc.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_publish.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_registry.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_results.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_sdk_compat.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_source_resolution.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_stages.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_unit4_golden.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_unit4_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_unit4_viz_ingest.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_unit4_workflow.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_verdict.py +0 -0
- {agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/tests/test_viz.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentic-dev-loop"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.0a7"
|
|
8
8
|
description = "Turn an LLM into a gated, sandboxed, resumable construction loop over your codebase."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
{agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/isolation.py
RENAMED
|
@@ -20,6 +20,7 @@ import json
|
|
|
20
20
|
import math
|
|
21
21
|
import os
|
|
22
22
|
import re
|
|
23
|
+
import shlex
|
|
23
24
|
import shutil
|
|
24
25
|
import subprocess
|
|
25
26
|
import time
|
|
@@ -66,6 +67,14 @@ AUDIT_MAX_BYTES = 5 * 1024 * 1024
|
|
|
66
67
|
# hung `setup:` hook can never wedge startup (BR-8 fail fast, not a hang).
|
|
67
68
|
PROVISION_TIMEOUT_SECONDS = 3600
|
|
68
69
|
|
|
70
|
+
# The sandbox venv the shipped pipeline relies on (016 sandbox-venv
|
|
71
|
+
# hardening). The engine probes this path INSIDE the exec container
|
|
72
|
+
# before trusting a warm provision stamp — a host-built (darwin) venv
|
|
73
|
+
# symlinks to a host interpreter that does not exist in the Linux
|
|
74
|
+
# container, so it must be detected and re-provisioned there.
|
|
75
|
+
SANDBOX_VENV_DIR = ".venv-sandbox"
|
|
76
|
+
SANDBOX_VENV_PYTHON = ".venv-sandbox/bin/python"
|
|
77
|
+
|
|
69
78
|
|
|
70
79
|
class SandboxError(RuntimeError):
|
|
71
80
|
"""A loud fail-fast sandbox failure; the message names the problem and
|
|
@@ -270,6 +279,58 @@ def setup_stamp_key(setup_cmd: str, lockfile_hashes: dict[str, str]) -> str:
|
|
|
270
279
|
json.dumps(canonical, sort_keys=True).encode("utf-8")).hexdigest()
|
|
271
280
|
|
|
272
281
|
|
|
282
|
+
def venv_probe_command() -> str:
|
|
283
|
+
"""In-container probe classifying the sandbox venv (016 self-heal).
|
|
284
|
+
|
|
285
|
+
Returns a shell command that prints exactly one of ``valid`` /
|
|
286
|
+
``missing`` / ``broken`` and exits 0. ``broken`` is the host-tampered
|
|
287
|
+
class: the python entry exists as a path (including a dangling symlink —
|
|
288
|
+
the host-darwin venv case) but is not executable/importable in the
|
|
289
|
+
container. ``missing`` means no such entry at all (the setup hook may
|
|
290
|
+
legitimately not create a venv), so it is treated as a stamp hit, never a
|
|
291
|
+
self-heal trigger.
|
|
292
|
+
"""
|
|
293
|
+
py = shlex.quote(SANDBOX_VENV_PYTHON)
|
|
294
|
+
return (
|
|
295
|
+
f"if [ -L {py} ] && [ ! -x {py} ]; then echo broken; "
|
|
296
|
+
f"elif [ ! -e {py} ]; then echo missing; "
|
|
297
|
+
f"elif {py} -c 'import sys' >/dev/null 2>&1; then echo valid; "
|
|
298
|
+
f"else echo broken; fi"
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def venv_probe_argv(container: str, workspace: str) -> list[str]:
|
|
303
|
+
"""``docker exec`` argv that runs the venv probe inside the (already
|
|
304
|
+
running) execution container against the bind-mounted workspace."""
|
|
305
|
+
return ["docker", "exec", "-w", workspace, container,
|
|
306
|
+
"/bin/sh", "-c", venv_probe_command()]
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def venv_probe_state(stdout: str, exit_code: int | None) -> str:
|
|
310
|
+
"""Classify a probe result. A nonzero exit (docker exec failed) is
|
|
311
|
+
``unknown`` — the caller leaves the stamp alone rather than guessing."""
|
|
312
|
+
if exit_code != 0:
|
|
313
|
+
return "unknown"
|
|
314
|
+
line = stdout.strip().splitlines()[-1] if stdout.strip() else ""
|
|
315
|
+
return line if line in ("valid", "missing", "broken") else "unknown"
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def remove_sandbox_venv(workspace: str | Path) -> None:
|
|
319
|
+
"""Remove the workspace's sandbox venv before re-provision (016 self-heal).
|
|
320
|
+
Fails loud when the venv exists but cannot be removed — a silent skip
|
|
321
|
+
would re-create the ENOENT failure the next run."""
|
|
322
|
+
venv = Path(workspace) / SANDBOX_VENV_DIR
|
|
323
|
+
try:
|
|
324
|
+
if venv.is_symlink() or venv.is_file():
|
|
325
|
+
venv.unlink()
|
|
326
|
+
elif venv.is_dir():
|
|
327
|
+
shutil.rmtree(venv)
|
|
328
|
+
except OSError as e:
|
|
329
|
+
raise SandboxError(
|
|
330
|
+
f"AE-SBX-014: cannot remove invalid sandbox venv at {venv}: {e} "
|
|
331
|
+
f"— remove it manually and re-run (docs/setup.md §5)") from e
|
|
332
|
+
|
|
333
|
+
|
|
273
334
|
def host_user_flag() -> str | None:
|
|
274
335
|
"""The ``docker --user`` value binding the sandbox to the host user.
|
|
275
336
|
|
|
@@ -507,9 +568,20 @@ class DockerBackend(Sandbox):
|
|
|
507
568
|
def provision(self, setup_cmd: str, stamp_key: str, stamp_dir: str,
|
|
508
569
|
force: bool) -> None:
|
|
509
570
|
"""Run one ``setup:`` hook in the provision container (network ON),
|
|
510
|
-
stamp-cached. Nonzero exit → AE-SBX-009, no stamp written.
|
|
571
|
+
stamp-cached. Nonzero exit → AE-SBX-009, no stamp written.
|
|
572
|
+
|
|
573
|
+
016 self-heal: a warm stamp no longer blind-skips. Before trusting it,
|
|
574
|
+
probe the sandbox venv inside the exec container — a host-built venv
|
|
575
|
+
(darwin symlinks to a host interpreter) looks valid on the host but is
|
|
576
|
+
broken in the Linux container. A ``broken`` probe removes the venv
|
|
577
|
+
(fail loud if removal is impossible) and falls through to re-provision;
|
|
578
|
+
``valid``/``missing``/``unknown`` keep the stamp skip.
|
|
579
|
+
"""
|
|
511
580
|
if not force and read_stamp(Path(stamp_dir), stamp_key):
|
|
512
|
-
|
|
581
|
+
if self._probe_venv() == "broken":
|
|
582
|
+
remove_sandbox_venv(self._workspace)
|
|
583
|
+
else:
|
|
584
|
+
return
|
|
513
585
|
payload = docker_provision_payload(
|
|
514
586
|
run_id=self._run_id, image=self._image, workspace=self._workspace,
|
|
515
587
|
setup_cmd=setup_cmd)
|
|
@@ -521,6 +593,12 @@ class DockerBackend(Sandbox):
|
|
|
521
593
|
f"— stamp not written (docs/setup.md §5)")
|
|
522
594
|
write_stamp(Path(stamp_dir), stamp_key)
|
|
523
595
|
|
|
596
|
+
def _probe_venv(self) -> str:
|
|
597
|
+
"""Run the in-container venv probe and classify it (016 self-heal)."""
|
|
598
|
+
res = self._runner.run(
|
|
599
|
+
venv_probe_argv(self._container, self._workspace), timeout=30)
|
|
600
|
+
return venv_probe_state(res.stdout, res.exit_code)
|
|
601
|
+
|
|
524
602
|
# -- execution ----------------------------------------------------------
|
|
525
603
|
|
|
526
604
|
def run(self, request: SandboxExecutionRequest) -> SandboxExecutionResult:
|
|
@@ -20,8 +20,9 @@ from agentic_dev_loop.engine.isolation import (
|
|
|
20
20
|
SandboxExecutionRequest, SandboxExecutionResult, append_audit_record,
|
|
21
21
|
clean_stamps, docker_create_payload, docker_provision_payload,
|
|
22
22
|
exec_container_name, is_git_mutation, probe_docker, read_stamp,
|
|
23
|
-
resolve_effective_profile, resolve_image_ref,
|
|
24
|
-
select_backend, setup_stamp_key, translate_mount_path,
|
|
23
|
+
remove_sandbox_venv, resolve_effective_profile, resolve_image_ref,
|
|
24
|
+
resolve_sandbox_env, select_backend, setup_stamp_key, translate_mount_path,
|
|
25
|
+
venv_probe_argv, venv_probe_command, venv_probe_state, write_stamp,
|
|
25
26
|
)
|
|
26
27
|
|
|
27
28
|
|
|
@@ -243,6 +244,66 @@ class TestSetupStampKey:
|
|
|
243
244
|
assert a == b
|
|
244
245
|
|
|
245
246
|
|
|
247
|
+
class TestVenvProbe:
|
|
248
|
+
def test_command_classifies_broken_symlink_first(self):
|
|
249
|
+
cmd = venv_probe_command()
|
|
250
|
+
# the dangling-symlink (host-darwin) case is caught before -e, which
|
|
251
|
+
# would otherwise report "missing"
|
|
252
|
+
assert "echo broken" in cmd and "echo missing" in cmd and "echo valid" in cmd
|
|
253
|
+
assert "-L .venv-sandbox/bin/python" in cmd
|
|
254
|
+
assert "import sys" in cmd
|
|
255
|
+
|
|
256
|
+
def test_argv_assembly(self):
|
|
257
|
+
argv = venv_probe_argv("c1", "/ws")
|
|
258
|
+
assert argv[:5] == ["docker", "exec", "-w", "/ws", "c1"]
|
|
259
|
+
assert argv[5:] == ["/bin/sh", "-c", venv_probe_command()]
|
|
260
|
+
|
|
261
|
+
@pytest.mark.parametrize("stdout,exit_code,expected", [
|
|
262
|
+
("valid\n", 0, "valid"),
|
|
263
|
+
("missing\n", 0, "missing"),
|
|
264
|
+
("broken\n", 0, "broken"),
|
|
265
|
+
("", 0, "unknown"),
|
|
266
|
+
("garbage", 0, "unknown"),
|
|
267
|
+
("valid", 1, "unknown"),
|
|
268
|
+
])
|
|
269
|
+
def test_state_classification(self, stdout, exit_code, expected):
|
|
270
|
+
assert venv_probe_state(stdout, exit_code) == expected
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class TestRemoveSandboxVenv:
|
|
274
|
+
def test_removes_broken_symlink_dir(self, tmp_path):
|
|
275
|
+
ws = tmp_path / "ws"
|
|
276
|
+
venv = ws / ".venv-sandbox"
|
|
277
|
+
(venv / "bin").mkdir(parents=True)
|
|
278
|
+
(venv / "bin" / "python").symlink_to("/opt/homebrew/opt/python/bin/python3.14")
|
|
279
|
+
remove_sandbox_venv(ws)
|
|
280
|
+
assert not venv.exists()
|
|
281
|
+
|
|
282
|
+
def test_removes_regular_dir(self, tmp_path):
|
|
283
|
+
ws = tmp_path / "ws"
|
|
284
|
+
venv = ws / ".venv-sandbox"
|
|
285
|
+
(venv / "lib").mkdir(parents=True)
|
|
286
|
+
(venv / "lib" / "x.txt").write_text("x")
|
|
287
|
+
remove_sandbox_venv(ws)
|
|
288
|
+
assert not venv.exists()
|
|
289
|
+
|
|
290
|
+
def test_missing_is_noop(self, tmp_path):
|
|
291
|
+
ws = tmp_path / "ws"
|
|
292
|
+
ws.mkdir()
|
|
293
|
+
remove_sandbox_venv(ws) # must not raise
|
|
294
|
+
|
|
295
|
+
def test_fails_loud_when_removal_impossible(self, tmp_path, monkeypatch):
|
|
296
|
+
ws = tmp_path / "ws"
|
|
297
|
+
(ws / ".venv-sandbox").mkdir(parents=True)
|
|
298
|
+
|
|
299
|
+
def boom(*args, **kwargs):
|
|
300
|
+
raise OSError("permission denied")
|
|
301
|
+
|
|
302
|
+
monkeypatch.setattr("agentic_dev_loop.engine.isolation.shutil.rmtree", boom)
|
|
303
|
+
with pytest.raises(SandboxError, match="AE-SBX-014"):
|
|
304
|
+
remove_sandbox_venv(ws)
|
|
305
|
+
|
|
306
|
+
|
|
246
307
|
class TestPayloads:
|
|
247
308
|
def _profile(self):
|
|
248
309
|
return EffectiveProfile(True, True, True, True, True, "img", "4g", 2.0)
|
|
@@ -650,12 +711,36 @@ class TestProvisionStampsAudit:
|
|
|
650
711
|
assert runner.calls
|
|
651
712
|
|
|
652
713
|
def test_provision_skips_on_stamp(self, tmp_path):
|
|
653
|
-
|
|
714
|
+
"""A warm stamp still skips, but only after the in-container venv
|
|
715
|
+
probe reports ``valid`` (016 self-heal) — no blind skip."""
|
|
716
|
+
runner = FakeRunner(ok("valid"))
|
|
654
717
|
backend = _backend(tmp_path, runner)
|
|
655
718
|
stamp_dir = tmp_path / "stamps"
|
|
656
719
|
write_stamp(stamp_dir, "key1")
|
|
657
720
|
backend.provision("make build", "key1", str(stamp_dir), force=False)
|
|
658
|
-
|
|
721
|
+
# the probe ran (docker exec); no provision container ran
|
|
722
|
+
assert [c for c in runner.calls if c[:2] == ["docker", "exec"]]
|
|
723
|
+
assert not [c for c in runner.calls if c[:2] == ["docker", "run"]]
|
|
724
|
+
|
|
725
|
+
def test_provision_self_heals_broken_venv_on_stamp(self, tmp_path):
|
|
726
|
+
"""016 repro (unit): stamp hit + broken venv → probe reports broken →
|
|
727
|
+
the venv is removed and the setup re-runs instead of blind-skipping."""
|
|
728
|
+
runner = FakeRunner(ok("broken"), ok(""))
|
|
729
|
+
backend = _backend(tmp_path, runner)
|
|
730
|
+
stamp_dir = tmp_path / "stamps"
|
|
731
|
+
write_stamp(stamp_dir, "key1")
|
|
732
|
+
ws = tmp_path / "ws"
|
|
733
|
+
venv_bin = ws / ".venv-sandbox" / "bin"
|
|
734
|
+
venv_bin.mkdir(parents=True)
|
|
735
|
+
# host-darwin venv: a python symlink whose target doesn't exist in the
|
|
736
|
+
# Linux container.
|
|
737
|
+
(venv_bin / "python").symlink_to("/opt/homebrew/opt/python/bin/python3.14")
|
|
738
|
+
backend.provision("python -m venv .venv-sandbox", "key1",
|
|
739
|
+
str(stamp_dir), force=False)
|
|
740
|
+
# broken venv removed, then provision re-ran
|
|
741
|
+
assert not (ws / ".venv-sandbox").exists()
|
|
742
|
+
assert [c for c in runner.calls if c[:2] == ["docker", "run"]]
|
|
743
|
+
assert read_stamp(stamp_dir, "key1")
|
|
659
744
|
|
|
660
745
|
def test_provision_refresh_forces(self, tmp_path):
|
|
661
746
|
runner = FakeRunner(ok(""))
|
|
@@ -180,3 +180,38 @@ class TestProvisionE2E:
|
|
|
180
180
|
b.provision("printf refreshed > stamp-probe", "k1", str(stamp_dir),
|
|
181
181
|
force=True)
|
|
182
182
|
assert (ws / "stamp-probe").read_text() == "refreshed"
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class TestVenvSelfHealE2E:
|
|
186
|
+
"""016 exact repro: a warm stamp plus a host-built (darwin) venv that is
|
|
187
|
+
broken inside the Linux container must self-heal — remove + re-provision —
|
|
188
|
+
instead of blind-skipping into a later ENOENT."""
|
|
189
|
+
|
|
190
|
+
def test_stamp_hit_with_host_venv_reprovisions(self, backend, tmp_path):
|
|
191
|
+
b, ws = backend
|
|
192
|
+
# Simulate a host-rebuilt venv: `bin/python` is a symlink to a host
|
|
193
|
+
# interpreter path that does not exist inside the Linux container.
|
|
194
|
+
venv_bin = ws / ".venv-sandbox" / "bin"
|
|
195
|
+
venv_bin.mkdir(parents=True)
|
|
196
|
+
(venv_bin / "python").symlink_to(
|
|
197
|
+
"/opt/homebrew/opt/python@3.14/bin/python3.14")
|
|
198
|
+
(venv_bin / "pip").symlink_to(
|
|
199
|
+
"/opt/homebrew/opt/python@3.14/bin/pip3.14")
|
|
200
|
+
|
|
201
|
+
stamp_dir = tmp_path / "stamps"
|
|
202
|
+
key = "warm-stamp"
|
|
203
|
+
write_stamp(stamp_dir, key)
|
|
204
|
+
|
|
205
|
+
b.provision("python -m venv .venv-sandbox", key, str(stamp_dir),
|
|
206
|
+
force=False)
|
|
207
|
+
|
|
208
|
+
# The broken venv was removed and re-provisioned: the sandbox python
|
|
209
|
+
# is now executable and importable, and the warm stamp still stands.
|
|
210
|
+
req = SandboxExecutionRequest(
|
|
211
|
+
argv=[".venv-sandbox/bin/python", "-c", "import sys; print('ok')"],
|
|
212
|
+
cwd=ws,
|
|
213
|
+
env={"PATH": "/usr/local/bin:/usr/bin:/bin", "HOME": "/tmp"})
|
|
214
|
+
result = b.run(req)
|
|
215
|
+
assert result.exit_code == 0, result.stderr
|
|
216
|
+
assert "ok" in result.stdout
|
|
217
|
+
assert read_stamp(stamp_dir, key)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/core/projection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/core/workflow.js.template
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/__init__.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/actions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/commit_shim.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/loaders.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/outputs.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/engine/registry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop/workflow/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop.egg-info/requires.txt
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a6 → agentic_dev_loop-1.0.0a7}/src/agentic_dev_loop.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|