agentic-dev-loop 1.0.0a39__tar.gz → 1.0.0a40__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.
Potentially problematic release.
This version of agentic-dev-loop might be problematic. Click here for more details.
- {agentic_dev_loop-1.0.0a39/src/agentic_dev_loop.egg-info → agentic_dev_loop-1.0.0a40}/PKG-INFO +1 -1
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/pyproject.toml +1 -1
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/core/config.py +4 -1
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/stages.py +25 -6
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/viz/server.py +17 -6
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40/src/agentic_dev_loop.egg-info}/PKG-INFO +1 -1
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_config.py +37 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_qa_prompt.py +16 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_stages.py +87 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_verdict.py +24 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_viz.py +20 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/LICENSE +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/README.md +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/setup.cfg +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/core/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/core/paths.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/core/results.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/core/verdict.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/actions.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/agent_error_guide.md +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/agent_error_guide.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/agents.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/allowlist.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/brief_coverage.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/cli.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/deps.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/dryrun.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/egress_policy.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/egress_proxy.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/forbidden.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/graph.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/isolation.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/llm.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/loaders.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/mounts.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/outputs.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/presets.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/proc.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/registry.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/seccomp/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/seccomp/chromium-userns.json +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/secrets.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/step_workspace.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/sync_back.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/py.typed +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/viz/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/viz/__main__.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop.egg-info/SOURCES.txt +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_028_declared_caches.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_029_merge_on_conflict.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_032_stop_when_green.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_033_brief_step.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_035_finalize_nudge.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_036_banned_commands_and_lane_boundary.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_041_brief_coverage.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_actions.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_agent_error_guide.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_agents.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_cli.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_deps.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_dry_run.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_e2e_fixtures.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_egress_proxy.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_graph.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_hostboundary_integration.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_isolation.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_isolation_integration.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_isolation_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_llm.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_loaders.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_module_contract.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_outputs.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_outputs_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_proc.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_publish.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_registry.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_repo_ascii.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_results.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_sdk_compat.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_secrets.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_secrets_integration.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_secrets_pipeline.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_source_resolution.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_step_workspace.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_sync_back.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_trigger_script.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_unit5_config.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_unit5_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_unit5_pure.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.0a40"
|
|
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"
|
|
@@ -148,6 +148,7 @@ class VerdictSpec(BaseModel):
|
|
|
148
148
|
pass_: str = Field(alias="pass")
|
|
149
149
|
fail: str = Field(alias="fail")
|
|
150
150
|
unverifiable: str | None = None
|
|
151
|
+
env: str | None = None
|
|
151
152
|
|
|
152
153
|
|
|
153
154
|
class Route(BaseModel):
|
|
@@ -404,6 +405,7 @@ class StepConfig(BaseModel):
|
|
|
404
405
|
on_pass: RouteTarget | None = None
|
|
405
406
|
on_fail: RouteTarget | None = None
|
|
406
407
|
on_unverifiable: RouteTarget | None = None
|
|
408
|
+
on_env: RouteTarget | None = None
|
|
407
409
|
|
|
408
410
|
@property
|
|
409
411
|
def kind(self) -> str:
|
|
@@ -794,7 +796,8 @@ class PipelineConfig(BaseModel):
|
|
|
794
796
|
raise ValueError(f"step name {n!r} collides with a reserved terminal")
|
|
795
797
|
valid = set(names) | TERMINALS
|
|
796
798
|
for step in self.steps:
|
|
797
|
-
for route in (step.on_pass, step.on_fail, step.on_unverifiable
|
|
799
|
+
for route in (step.on_pass, step.on_fail, step.on_unverifiable,
|
|
800
|
+
step.on_env):
|
|
798
801
|
target = route_target(route)
|
|
799
802
|
if target is not None and target not in valid:
|
|
800
803
|
raise ValueError(
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/stages.py
RENAMED
|
@@ -683,13 +683,13 @@ def _unclear_note(runtime: Runtime, step: StepConfig, attempt: int,
|
|
|
683
683
|
captured response when it exists, so real findings are never swallowed."""
|
|
684
684
|
expected = ""
|
|
685
685
|
if step.verdicts is not None:
|
|
686
|
+
keywords = [f"`VERDICT: {step.verdicts.pass_}`",
|
|
687
|
+
f"`VERDICT: {step.verdicts.fail}`"]
|
|
686
688
|
if step.verdicts.unverifiable is not None:
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
expected = (f" (expected `VERDICT: {step.verdicts.pass_}` or "
|
|
692
|
-
f"`VERDICT: {step.verdicts.fail}` on its own line)")
|
|
689
|
+
keywords.append(f"`VERDICT: {step.verdicts.unverifiable}`")
|
|
690
|
+
if step.verdicts.env is not None:
|
|
691
|
+
keywords.append(f"`VERDICT: {step.verdicts.env}`")
|
|
692
|
+
expected = f" (expected {' or '.join(keywords)} on its own line)"
|
|
693
693
|
if truncated:
|
|
694
694
|
lead = (f"{step.name} agent was cut short on attempt {attempt} "
|
|
695
695
|
f"(hit the {max_iterations}-turn limit or was stopped "
|
|
@@ -718,6 +718,8 @@ def make_agent_node(runtime: Runtime, step: StepConfig):
|
|
|
718
718
|
verdicts_map = {step.verdicts.pass_: "pass", step.verdicts.fail: "fail"}
|
|
719
719
|
if step.verdicts.unverifiable is not None:
|
|
720
720
|
verdicts_map[step.verdicts.unverifiable] = "unverifiable"
|
|
721
|
+
if step.verdicts.env is not None:
|
|
722
|
+
verdicts_map[step.verdicts.env] = "env"
|
|
721
723
|
captures = step.artifact is not None or bool(step.produces)
|
|
722
724
|
|
|
723
725
|
def node(state: dict) -> dict:
|
|
@@ -787,6 +789,21 @@ def make_agent_node(runtime: Runtime, step: StepConfig):
|
|
|
787
789
|
if captures and result.response is not None:
|
|
788
790
|
update["artifacts"] = {step.name: result.response}
|
|
789
791
|
return update
|
|
792
|
+
if verdict == "env":
|
|
793
|
+
print(f"[{subject}] {step.name} verdict: ENV "
|
|
794
|
+
f"(environmental, halting).", flush=True)
|
|
795
|
+
if step.on_env is None:
|
|
796
|
+
runtime.state_dir.mkdir(parents=True, exist_ok=True)
|
|
797
|
+
(runtime.state_dir / LAST_ERROR).write_text(
|
|
798
|
+
f"# Environmental failure (verdict: ENV)\n\n"
|
|
799
|
+
f"{result.response or 'no captured response'}\n",
|
|
800
|
+
encoding="utf-8")
|
|
801
|
+
return {"step_verdicts": merged,
|
|
802
|
+
"failed": True, "failed_step": step.name}
|
|
803
|
+
update = {"step_verdicts": merged, "failed": False}
|
|
804
|
+
if captures and result.response is not None:
|
|
805
|
+
update["artifacts"] = {step.name: result.response}
|
|
806
|
+
return update
|
|
790
807
|
print(f"[{subject}] {step.name} verdict: fail on attempt {attempt}.",
|
|
791
808
|
flush=True)
|
|
792
809
|
update = {"step_verdicts": merged,
|
|
@@ -1115,6 +1132,8 @@ def make_router(runtime: Runtime, step: StepConfig):
|
|
|
1115
1132
|
if step.on_unverifiable is not None:
|
|
1116
1133
|
return resolve(step.on_unverifiable)
|
|
1117
1134
|
return resolve(step.on_pass)
|
|
1135
|
+
if verdict == "env":
|
|
1136
|
+
return resolve(step.on_env) if step.on_env is not None else END
|
|
1118
1137
|
if verdict == "fail":
|
|
1119
1138
|
if step.on_fail is None:
|
|
1120
1139
|
return END
|
|
@@ -546,6 +546,7 @@ PAGE = """<!doctype html>
|
|
|
546
546
|
var verdictFail = new Set();
|
|
547
547
|
var verdictPass = new Set();
|
|
548
548
|
var verdictUnverifiable = new Set();
|
|
549
|
+
var verdictEnv = new Set();
|
|
549
550
|
var fatalNodes = new Set();
|
|
550
551
|
var maxAttempts = null;
|
|
551
552
|
var attempt = null;
|
|
@@ -614,9 +615,15 @@ PAGE = """<!doctype html>
|
|
|
614
615
|
if (verdictFail.has(n)) { return "fail"; }
|
|
615
616
|
if (verdictPass.has(n)) { return "pass"; }
|
|
616
617
|
if (verdictUnverifiable.has(n)) { return "unverifiable"; }
|
|
618
|
+
if (verdictEnv.has(n)) { return "env"; }
|
|
617
619
|
return nodeStatus[n] || "pending";
|
|
618
620
|
}
|
|
619
621
|
|
|
622
|
+
function renderStatusOf(n) {
|
|
623
|
+
var s = statusOf(n);
|
|
624
|
+
return s === "env" ? "fatal" : s;
|
|
625
|
+
}
|
|
626
|
+
|
|
620
627
|
function updateBadges() {
|
|
621
628
|
var box = el("verdicts");
|
|
622
629
|
box.innerHTML = "";
|
|
@@ -627,6 +634,7 @@ PAGE = """<!doctype html>
|
|
|
627
634
|
if (v === "pass") { b.className += " badge-pass"; }
|
|
628
635
|
else if (v === "fail") { b.className += " badge-fail"; }
|
|
629
636
|
else if (v === "unverifiable") { b.className += " badge-unverifiable"; }
|
|
637
|
+
else if (v === "env") { b.className += " badge-fail"; }
|
|
630
638
|
else { b.className += " badge-unclear"; }
|
|
631
639
|
b.textContent = step + ": " + (v === null || v === undefined ? "no verdict" : v);
|
|
632
640
|
box.appendChild(b);
|
|
@@ -638,6 +646,7 @@ PAGE = """<!doctype html>
|
|
|
638
646
|
verdictPass.clear();
|
|
639
647
|
verdictFail.clear();
|
|
640
648
|
verdictUnverifiable.clear();
|
|
649
|
+
verdictEnv.clear();
|
|
641
650
|
currentVerdicts = {};
|
|
642
651
|
nodeStatus = {};
|
|
643
652
|
stageWord = {};
|
|
@@ -665,7 +674,7 @@ PAGE = """<!doctype html>
|
|
|
665
674
|
var classLines = Object.keys(nodeStatus).filter(function (n) {
|
|
666
675
|
return nodeIds.has(n);
|
|
667
676
|
}).map(function (n) {
|
|
668
|
-
return "class " + n + " " +
|
|
677
|
+
return "class " + n + " " + renderStatusOf(n) + ";";
|
|
669
678
|
});
|
|
670
679
|
var src = mermaidSource + "\\n" + classDefs.join("\\n") + "\\n" + classLines.join("\\n");
|
|
671
680
|
mermaid.render("graph-" + (renderCount++), src).then(function (res) {
|
|
@@ -756,9 +765,10 @@ PAGE = """<!doctype html>
|
|
|
756
765
|
if (v === "pass") { suffix = " (verdict: PASS)"; }
|
|
757
766
|
else if (v === "fail") { suffix = " (verdict: FAIL)"; }
|
|
758
767
|
else if (v === "unverifiable") { suffix = " (verdict: UNVERIFIABLE)"; }
|
|
768
|
+
else if (v === "env") { suffix = " (verdict: ENV)"; }
|
|
759
769
|
}
|
|
760
770
|
status.textContent = (word ? " " + word : "") + suffix;
|
|
761
|
-
header.className = "stage-header status-" +
|
|
771
|
+
header.className = "stage-header status-" + renderStatusOf(node);
|
|
762
772
|
}
|
|
763
773
|
|
|
764
774
|
function setStageStatus(node, word) {
|
|
@@ -1072,10 +1082,11 @@ PAGE = """<!doctype html>
|
|
|
1072
1082
|
Object.keys(ev.step_verdicts).forEach(function (k) {
|
|
1073
1083
|
verdictSteps.add(k);
|
|
1074
1084
|
var v = ev.step_verdicts[k];
|
|
1075
|
-
if (v === "pass") { verdictPass.add(k); verdictFail.delete(k); verdictUnverifiable.delete(k); }
|
|
1076
|
-
else if (v === "fail") { verdictFail.add(k); verdictPass.delete(k); verdictUnverifiable.delete(k); }
|
|
1077
|
-
else if (v === "unverifiable") { verdictUnverifiable.add(k); verdictPass.delete(k); verdictFail.delete(k); }
|
|
1078
|
-
else { verdictPass.delete(k); verdictFail.delete(k); verdictUnverifiable.delete(k); }
|
|
1085
|
+
if (v === "pass") { verdictPass.add(k); verdictFail.delete(k); verdictUnverifiable.delete(k); verdictEnv.delete(k); }
|
|
1086
|
+
else if (v === "fail") { verdictFail.add(k); verdictPass.delete(k); verdictUnverifiable.delete(k); verdictEnv.delete(k); }
|
|
1087
|
+
else if (v === "unverifiable") { verdictUnverifiable.add(k); verdictPass.delete(k); verdictFail.delete(k); verdictEnv.delete(k); }
|
|
1088
|
+
else if (v === "env") { verdictEnv.add(k); verdictPass.delete(k); verdictFail.delete(k); verdictUnverifiable.delete(k); }
|
|
1089
|
+
else { verdictPass.delete(k); verdictFail.delete(k); verdictUnverifiable.delete(k); verdictEnv.delete(k); }
|
|
1079
1090
|
currentVerdicts[k] = v;
|
|
1080
1091
|
});
|
|
1081
1092
|
updateBadges();
|
|
@@ -637,3 +637,40 @@ class TestDepsPolicy:
|
|
|
637
637
|
def test_lockfiles_accepts_a_single_string(self, tmp_path):
|
|
638
638
|
data = _base() | {"lockfiles": "uv.lock"}
|
|
639
639
|
assert _load(tmp_path, data).lockfiles == ["uv.lock"]
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
def test_four_keyword_verdict_and_on_env_load(tmp_path):
|
|
643
|
+
data = _base()
|
|
644
|
+
data["steps"][0] = {
|
|
645
|
+
"name": "qa", "agent": "qa", "artifact": "qa-report.md",
|
|
646
|
+
"verdicts": {"pass": "PASS", "fail": "FAIL",
|
|
647
|
+
"unverifiable": "UNVERIFIABLE", "env": "ENV"},
|
|
648
|
+
"on_pass": "success", "on_unverifiable": "success",
|
|
649
|
+
"on_env": "failure",
|
|
650
|
+
}
|
|
651
|
+
step = _load(tmp_path, data).steps[0]
|
|
652
|
+
assert step.verdicts.pass_ == "PASS"
|
|
653
|
+
assert step.verdicts.fail == "FAIL"
|
|
654
|
+
assert step.verdicts.unverifiable == "UNVERIFIABLE"
|
|
655
|
+
assert step.verdicts.env == "ENV"
|
|
656
|
+
assert step.on_env == "failure"
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
def test_dangling_on_env_rejected(tmp_path):
|
|
660
|
+
data = _base()
|
|
661
|
+
data["steps"][0]["on_env"] = "nope"
|
|
662
|
+
with pytest.raises(ValidationError, match="unknown route target 'nope'"):
|
|
663
|
+
_load(tmp_path, data)
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
def test_env_keyword_optional_three_keyword_still_loads(tmp_path):
|
|
667
|
+
data = _base()
|
|
668
|
+
data["steps"][0] = {
|
|
669
|
+
"name": "qa", "agent": "qa", "artifact": "qa-report.md",
|
|
670
|
+
"verdicts": {"pass": "PASS", "fail": "FAIL",
|
|
671
|
+
"unverifiable": "UNVERIFIABLE"},
|
|
672
|
+
"on_pass": "success", "on_unverifiable": "success",
|
|
673
|
+
}
|
|
674
|
+
step = _load(tmp_path, data).steps[0]
|
|
675
|
+
assert step.verdicts.env is None
|
|
676
|
+
assert step.on_env is None
|
|
@@ -35,3 +35,19 @@ def test_probe_plan_rules_present():
|
|
|
35
35
|
assert "**Outcome probes.**" in text
|
|
36
36
|
assert "**Concurrency probes.**" in text
|
|
37
37
|
assert "**Error messages are assertions.**" in text
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_env_verdict_rules_present():
|
|
41
|
+
text = _text()
|
|
42
|
+
assert "VERDICT: ENV" in text
|
|
43
|
+
assert "## ENV" in text
|
|
44
|
+
assert "environment" in text.lower()
|
|
45
|
+
assert "harness" in text
|
|
46
|
+
assert "2 attempts" in text
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_env_unverifiable_distinction_present():
|
|
50
|
+
text = _text()
|
|
51
|
+
assert "UNVERIFIABLE" in text
|
|
52
|
+
assert "verification environment" in text
|
|
53
|
+
assert "product code" in text
|
|
@@ -362,6 +362,53 @@ class TestAgentNode:
|
|
|
362
362
|
assert "VERDICT: FAIL" in note
|
|
363
363
|
assert "VERDICT: UNVERIFIABLE" in note
|
|
364
364
|
|
|
365
|
+
def test_unclear_note_mentions_fourth_keyword(self, runtime, fake_agents):
|
|
366
|
+
step = make_cfg([
|
|
367
|
+
{"name": "qa", "agent": "qa", "artifact": "qa-report.md",
|
|
368
|
+
"verdicts": {"pass": "PASS", "fail": "FAIL",
|
|
369
|
+
"unverifiable": "UNVERIFIABLE", "env": "ENV"},
|
|
370
|
+
"on_pass": "success"}]).steps[0]
|
|
371
|
+
fake_agents.set("qa", [("write", "qa-report.md", "undecided")])
|
|
372
|
+
update = make_agent_node(runtime, step)(make_state())
|
|
373
|
+
(note,) = update["notes"]
|
|
374
|
+
assert "VERDICT: PASS" in note
|
|
375
|
+
assert "VERDICT: FAIL" in note
|
|
376
|
+
assert "VERDICT: UNVERIFIABLE" in note
|
|
377
|
+
assert "VERDICT: ENV" in note
|
|
378
|
+
|
|
379
|
+
def test_env_verdict_hard_fails_and_writes_last_error(self, runtime,
|
|
380
|
+
fake_agents):
|
|
381
|
+
step = make_cfg([
|
|
382
|
+
{"name": "qa", "agent": "qa", "artifact": "qa-report.md",
|
|
383
|
+
"verdicts": {"pass": "PASS", "fail": "FAIL",
|
|
384
|
+
"unverifiable": "UNVERIFIABLE", "env": "ENV"},
|
|
385
|
+
"on_pass": "success"}]).steps[0]
|
|
386
|
+
fake_agents.set("qa", [("write", "qa-report.md",
|
|
387
|
+
"probe timeout\nVERDICT: ENV")])
|
|
388
|
+
update = make_agent_node(runtime, step)(make_state())
|
|
389
|
+
assert update["step_verdicts"] == {"qa": "env"}
|
|
390
|
+
assert update["failed"] is True
|
|
391
|
+
assert update["failed_step"] == "qa"
|
|
392
|
+
assert "artifacts" not in update
|
|
393
|
+
err = (runtime.state_dir / "last-error.md").read_text()
|
|
394
|
+
assert "Environmental failure (verdict: ENV)" in err
|
|
395
|
+
assert "probe timeout" in err
|
|
396
|
+
|
|
397
|
+
def test_env_verdict_with_on_env_route_moves_without_hard_fail(
|
|
398
|
+
self, runtime, fake_agents):
|
|
399
|
+
step = make_cfg([
|
|
400
|
+
{"name": "qa", "agent": "qa", "artifact": "qa-report.md",
|
|
401
|
+
"verdicts": {"pass": "PASS", "fail": "FAIL",
|
|
402
|
+
"unverifiable": "UNVERIFIABLE", "env": "ENV"},
|
|
403
|
+
"on_pass": "success", "on_env": "failure"}]).steps[0]
|
|
404
|
+
fake_agents.set("qa", [("write", "qa-report.md",
|
|
405
|
+
"probe timeout\nVERDICT: ENV")])
|
|
406
|
+
update = make_agent_node(runtime, step)(make_state())
|
|
407
|
+
assert update["step_verdicts"] == {"qa": "env"}
|
|
408
|
+
assert update["failed"] is False
|
|
409
|
+
assert update["artifacts"] == {"qa": "probe timeout\nVERDICT: ENV"}
|
|
410
|
+
assert "failed_step" not in update
|
|
411
|
+
|
|
365
412
|
|
|
366
413
|
class TestCommandNode:
|
|
367
414
|
def _node(self, runtime, steps, idx=0):
|
|
@@ -640,6 +687,46 @@ class TestRouter:
|
|
|
640
687
|
assert router(make_state(attempt=1,
|
|
641
688
|
step_verdicts={"qa": "unverifiable"})) == "commit"
|
|
642
689
|
|
|
690
|
+
def test_env_routes_on_env(self, runtime):
|
|
691
|
+
cfg = make_cfg([
|
|
692
|
+
{"name": "dev", "agent": "dev", "on_pass": "qa"},
|
|
693
|
+
{"name": "qa", "agent": "qa", "artifact": "qa-report.md",
|
|
694
|
+
"verdicts": {"pass": "PASS", "fail": "FAIL",
|
|
695
|
+
"unverifiable": "UNVERIFIABLE", "env": "ENV"},
|
|
696
|
+
"on_pass": "commit", "on_fail": {"goto": "dev", "retry": True},
|
|
697
|
+
"on_env": "dev"},
|
|
698
|
+
{"name": "commit", "action": "commit", "on_pass": "success"},
|
|
699
|
+
])
|
|
700
|
+
runtime = dataclasses.replace(runtime, cfg=cfg)
|
|
701
|
+
router = make_router(runtime, cfg.steps[1])
|
|
702
|
+
assert router(make_state(step_verdicts={"qa": "env"})) == "dev"
|
|
703
|
+
|
|
704
|
+
def test_env_with_on_env_failure_ends(self, runtime):
|
|
705
|
+
cfg = make_cfg([
|
|
706
|
+
{"name": "dev", "agent": "dev", "on_pass": "qa"},
|
|
707
|
+
{"name": "qa", "agent": "qa", "artifact": "qa-report.md",
|
|
708
|
+
"verdicts": {"pass": "PASS", "fail": "FAIL",
|
|
709
|
+
"unverifiable": "UNVERIFIABLE", "env": "ENV"},
|
|
710
|
+
"on_pass": "commit", "on_env": "failure"},
|
|
711
|
+
{"name": "commit", "action": "commit", "on_pass": "success"},
|
|
712
|
+
])
|
|
713
|
+
runtime = dataclasses.replace(runtime, cfg=cfg)
|
|
714
|
+
router = make_router(runtime, cfg.steps[1])
|
|
715
|
+
assert router(make_state(step_verdicts={"qa": "env"})) == END
|
|
716
|
+
|
|
717
|
+
def test_env_without_on_env_is_defensive_end(self, runtime):
|
|
718
|
+
cfg = make_cfg([
|
|
719
|
+
{"name": "dev", "agent": "dev", "on_pass": "qa"},
|
|
720
|
+
{"name": "qa", "agent": "qa", "artifact": "qa-report.md",
|
|
721
|
+
"verdicts": {"pass": "PASS", "fail": "FAIL",
|
|
722
|
+
"unverifiable": "UNVERIFIABLE", "env": "ENV"},
|
|
723
|
+
"on_pass": "commit"},
|
|
724
|
+
{"name": "commit", "action": "commit", "on_pass": "success"},
|
|
725
|
+
])
|
|
726
|
+
runtime = dataclasses.replace(runtime, cfg=cfg)
|
|
727
|
+
router = make_router(runtime, cfg.steps[1])
|
|
728
|
+
assert router(make_state(step_verdicts={"qa": "env"})) == END
|
|
729
|
+
|
|
643
730
|
|
|
644
731
|
class TestBumpNode:
|
|
645
732
|
def test_consumes_attempt_resets_verdicts_rebuilds_cleans(self, runtime):
|
|
@@ -217,3 +217,27 @@ class TestVerdictShape:
|
|
|
217
217
|
a = _parse("VERDICT: PASS", TEST_VERDICTS).value
|
|
218
218
|
b = _parse("VERDICT: **PASS**", TEST_VERDICTS).value
|
|
219
219
|
assert a == b
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
QA_ENV_VERDICTS = {"PASS": "pass", "FAIL": "fail",
|
|
223
|
+
"UNVERIFIABLE": "unverifiable", "ENV": "env"}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class TestEnvVerdictParsing:
|
|
227
|
+
def test_parse_env(self):
|
|
228
|
+
_ok("VERDICT: ENV", QA_ENV_VERDICTS, "env")
|
|
229
|
+
_ok("VERDICT: ENV - sandbox network is dead",
|
|
230
|
+
QA_ENV_VERDICTS, "env")
|
|
231
|
+
_ok("verdict: env", QA_ENV_VERDICTS, "env")
|
|
232
|
+
|
|
233
|
+
def test_parse_verdict_env_returns_canonical_value(self):
|
|
234
|
+
assert parse_verdict("VERDICT: ENV", QA_ENV_VERDICTS) == "env"
|
|
235
|
+
|
|
236
|
+
def test_negated_env_stays_ambiguous_on_four_key_sets(self):
|
|
237
|
+
_err("VERDICT: NOT ENV", QA_ENV_VERDICTS, VerdictError.AMBIGUOUS)
|
|
238
|
+
|
|
239
|
+
def test_conflicting_pass_and_env_is_ambiguous(self):
|
|
240
|
+
_err("VERDICT: PASS\nVERDICT: ENV", QA_ENV_VERDICTS,
|
|
241
|
+
VerdictError.AMBIGUOUS)
|
|
242
|
+
assert parse_verdict("VERDICT: PASS\nVERDICT: ENV",
|
|
243
|
+
QA_ENV_VERDICTS) == "retry"
|
|
@@ -743,6 +743,13 @@ class TestServer:
|
|
|
743
743
|
assert 'else if (v === "unverifiable")' in PAGE
|
|
744
744
|
assert " (verdict: UNVERIFIABLE)" in PAGE
|
|
745
745
|
|
|
746
|
+
def test_page_constant_has_env_verdict_support(self):
|
|
747
|
+
assert "verdictEnv" in PAGE
|
|
748
|
+
# the new value maps to the fatal badge, never the unclear fallthrough
|
|
749
|
+
assert 'else if (v === "env")' in PAGE
|
|
750
|
+
assert " (verdict: ENV)" in PAGE
|
|
751
|
+
assert 'verdictEnv.has(n)' in PAGE
|
|
752
|
+
|
|
746
753
|
def test_page_constant_color_palette(self):
|
|
747
754
|
assert 'classDef running fill:#1976d2' in PAGE
|
|
748
755
|
assert 'classDef fail fill:#ffb300' in PAGE
|
|
@@ -1119,6 +1126,19 @@ main();
|
|
|
1119
1126
|
assert snaps[0]["badges"] == ["review: unverifiable"]
|
|
1120
1127
|
assert snaps[0]["classes"]["review"] == "unverifiable"
|
|
1121
1128
|
|
|
1129
|
+
def test_env_verdict_badge_and_fatal_class(self, node, tmp_path):
|
|
1130
|
+
events = [
|
|
1131
|
+
{"type": "run_started", "subject": "s", "max_attempts": 5,
|
|
1132
|
+
"attempt": 1, "thread_id": "t"},
|
|
1133
|
+
{"type": "node_finished", "node": "review", "ok": True},
|
|
1134
|
+
{"type": "state", "attempt": 1,
|
|
1135
|
+
"step_verdicts": {"review": "env"}, "failed": False},
|
|
1136
|
+
]
|
|
1137
|
+
snaps = self._run_page(node, events, tmp_path)
|
|
1138
|
+
|
|
1139
|
+
assert snaps[0]["badges"] == ["review: env"]
|
|
1140
|
+
assert snaps[0]["classes"]["review"] == "fatal"
|
|
1141
|
+
|
|
1122
1142
|
def test_copy_button_copies_console_text(self, node, tmp_path):
|
|
1123
1143
|
events = [
|
|
1124
1144
|
{"type": "run_started", "subject": "s", "max_attempts": 5,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/core/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/core/results.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/core/verdict.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/__init__.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/actions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/agents.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/allowlist.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/dryrun.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/egress_policy.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/egress_proxy.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/forbidden.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/graph.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/hostboundary.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/isolation.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/loaders.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/mounts.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/outputs.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/presets.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/registry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/secrets.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/engine/sync_back.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/viz/__init__.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop/viz/__main__.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/src/agentic_dev_loop.egg-info/requires.txt
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/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
|
{agentic_dev_loop-1.0.0a39 → agentic_dev_loop-1.0.0a40}/tests/test_hostboundary_integration.py
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
|