agentic-dev-loop 1.0.0a16__tar.gz → 1.0.0a17__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.0a16 → agentic_dev_loop-1.0.0a17}/PKG-INFO +2 -2
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/README.md +1 -1
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/pyproject.toml +1 -1
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/actions.py +1 -1
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/graph.py +16 -7
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/hostboundary.py +1 -1
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/stages.py +53 -14
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop.egg-info/PKG-INFO +2 -2
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_actions.py +1 -1
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_agents.py +1 -1
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_config.py +3 -3
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_graph.py +35 -10
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_qa_prompt.py +8 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_secrets.py +4 -4
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_stages.py +64 -11
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/LICENSE +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/setup.cfg +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/core/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/core/config.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/core/results.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/core/verdict.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/agents.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/cli.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/deps.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/dryrun.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/isolation.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/llm.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/loaders.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/outputs.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/proc.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/registry.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/seccomp/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/seccomp/chromium-userns.json +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/secrets.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/py.typed +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/viz/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/viz/__main__.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/viz/server.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop.egg-info/SOURCES.txt +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_cli.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_deps.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_dry_run.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_e2e_fixtures.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_hostboundary_integration.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_isolation.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_isolation_integration.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_isolation_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_llm.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_loaders.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_module_contract.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_outputs.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_outputs_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_proc.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_publish.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_registry.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_repo_ascii.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_results.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_sdk_compat.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_secrets_integration.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_secrets_pipeline.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_source_resolution.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_trigger_script.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_verdict.py +0 -0
- {agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/tests/test_viz.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentic-dev-loop
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.0a17
|
|
4
4
|
Summary: Turn an LLM into a gated, sandboxed, resumable construction loop over your codebase.
|
|
5
5
|
Author: Luke Wilson
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -231,7 +231,7 @@ secrets: # refs only - never values
|
|
|
231
231
|
key: KINDIE_CLIENT_SECRET # provider-specific locator
|
|
232
232
|
r2-keys:
|
|
233
233
|
from: env
|
|
234
|
-
key:
|
|
234
|
+
key: R2_KEYS
|
|
235
235
|
|
|
236
236
|
steps:
|
|
237
237
|
- name: qa
|
|
@@ -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.0a17"
|
|
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.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/actions.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Built-in pipeline actions. Node-shaped: (state, params) -> state update.
|
|
2
2
|
|
|
3
|
-
`commit` policy (ported unchanged from
|
|
3
|
+
`commit` policy (ported unchanged from the consumer): agents never commit mid-run;
|
|
4
4
|
the pipeline commits exactly once, and only when the worktree was clean at
|
|
5
5
|
run start - a dirty-at-start run is committed by the human, so the
|
|
6
6
|
auto-commit can never swallow pre-existing unrelated changes (run 004: the
|
{agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/graph.py
RENAMED
|
@@ -27,19 +27,27 @@ from .loaders import resolve_docs
|
|
|
27
27
|
from .secrets import resolve_secrets, scan_env_files
|
|
28
28
|
from .registry import (Registry, default_registry, load_extensions,
|
|
29
29
|
validate_action_network)
|
|
30
|
-
from .stages import (Runtime,
|
|
30
|
+
from .stages import (Runtime, clear_attempt_artifacts,
|
|
31
31
|
make_action_node, make_agent_node, make_bump_node,
|
|
32
32
|
make_command_node, make_router, success_node)
|
|
33
33
|
|
|
34
34
|
CHECKPOINT_DB = "loop-checkpoints.sqlite" # under state_dir, one DB per workspace
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
def _merge_artifacts(a: dict[str, str], b: dict[str, str]) -> dict[str, str]:
|
|
38
|
+
"""Accumulate per-step artifact text across nodes. LangGraph gives
|
|
39
|
+
un-annotated fields last-write-wins, so without this reducer one node's
|
|
40
|
+
entry would silently replace the accumulated dict."""
|
|
41
|
+
return {**a, **b}
|
|
42
|
+
|
|
43
|
+
|
|
37
44
|
class LoopState(TypedDict):
|
|
38
45
|
"""State threaded through the graph. `notes` accumulates across attempts
|
|
39
|
-
(append reducer)
|
|
46
|
+
(append reducer), `artifacts` accumulates across stages (merge reducer);
|
|
47
|
+
every other key is last-write-wins."""
|
|
40
48
|
subject: str
|
|
41
49
|
docs: dict[str, str]
|
|
42
|
-
|
|
50
|
+
artifacts: Annotated[dict[str, str], _merge_artifacts] # step name -> artifact text
|
|
43
51
|
attempt: int
|
|
44
52
|
max_attempts: int
|
|
45
53
|
notes: Annotated[list[str], operator.add]
|
|
@@ -95,7 +103,7 @@ def run_pipeline(cfg_path, args, *, viz_bus=None,
|
|
|
95
103
|
registry: Registry | None = None) -> tuple[bool, int]:
|
|
96
104
|
"""Load a pipeline YAML, resolve docs via the selected document loader,
|
|
97
105
|
and run the graph to completion. Returns (success, attempts) - the
|
|
98
|
-
public API,
|
|
106
|
+
public API, the consumer's `construct`/`resume_loop` equivalent.
|
|
99
107
|
|
|
100
108
|
`args` carries the parsed CLI namespace: loader flags, --resume,
|
|
101
109
|
--max-attempts (which overrides the YAML value when set), and --no-viz.
|
|
@@ -163,7 +171,7 @@ def _run_pipeline(cfg, base, registry, state_dir, max_attempts, args, viz_bus,
|
|
|
163
171
|
config = {"configurable": {"thread_id": thread_id}}
|
|
164
172
|
db = str(state_dir / CHECKPOINT_DB)
|
|
165
173
|
if resume:
|
|
166
|
-
initial = None # state (docs/subject/
|
|
174
|
+
initial = None # state (docs/subject/artifacts) comes from the checkpoint
|
|
167
175
|
else:
|
|
168
176
|
_loader, docs, subject = resolve_docs(cfg, base, registry, args)
|
|
169
177
|
# A fresh run owns state_dir: drop cross-run leftovers (a previous
|
|
@@ -173,7 +181,7 @@ def _run_pipeline(cfg, base, registry, state_dir, max_attempts, args, viz_bus,
|
|
|
173
181
|
initial: LoopState = {
|
|
174
182
|
"subject": subject,
|
|
175
183
|
"docs": docs,
|
|
176
|
-
"
|
|
184
|
+
"artifacts": {},
|
|
177
185
|
"attempt": 1,
|
|
178
186
|
"max_attempts": max_attempts,
|
|
179
187
|
"notes": [],
|
|
@@ -221,7 +229,8 @@ def _run_pipeline(cfg, base, registry, state_dir, max_attempts, args, viz_bus,
|
|
|
221
229
|
f"Failure details: {state_dir}/last-error.md",
|
|
222
230
|
flush=True)
|
|
223
231
|
initial = {**values, "failed": False,
|
|
224
|
-
"resume_target": target, "failed_step": None
|
|
232
|
+
"resume_target": target, "failed_step": None,
|
|
233
|
+
"artifacts": values.get("artifacts", {})}
|
|
225
234
|
|
|
226
235
|
if bus is None and not getattr(args, "no_viz", True):
|
|
227
236
|
start_state = initial if initial is not None \
|
{agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/hostboundary.py
RENAMED
|
@@ -29,7 +29,7 @@ if TYPE_CHECKING:
|
|
|
29
29
|
from agentic_dev_loop.core.config import PipelineConfig
|
|
30
30
|
|
|
31
31
|
# Tooling directories are workspace-root-relative fixed names (the same layout
|
|
32
|
-
# the dogfood pipeline and
|
|
32
|
+
# the dogfood pipeline and the consumer repo use); the other tooling paths are config- or
|
|
33
33
|
# pipeline-file-relative.
|
|
34
34
|
TOOLING_DIR_NAMES = ("aidlc-docs", "tools")
|
|
35
35
|
|
{agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/stages.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Stage nodes, routers, and the run_agent port.
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
The consumer loop's hardcoded stages become per-step closures built from the
|
|
4
4
|
pipeline config; the fixed policies stay here, not in config:
|
|
5
5
|
|
|
6
6
|
- per-attempt hygiene: every declared artifact/produces file + last-error.md
|
|
@@ -49,6 +49,7 @@ if TYPE_CHECKING:
|
|
|
49
49
|
DEFAULT_MAX_ITERATIONS = 500
|
|
50
50
|
DEFAULT_COMMAND_TIMEOUT = 600.0 # seconds - a pytest-class suite budget
|
|
51
51
|
LAST_ERROR = "last-error.md"
|
|
52
|
+
ARTIFACT_CONTEXT_CAP = 24_000 # chars per artifact injected into a step's context
|
|
52
53
|
|
|
53
54
|
|
|
54
55
|
@dataclass
|
|
@@ -68,7 +69,7 @@ class Runtime:
|
|
|
68
69
|
def prompt_dir(runtime: Runtime) -> str:
|
|
69
70
|
"""state_dir as agents must address it. Agents resolve prompt paths
|
|
70
71
|
against their workspace, so a bare `state_dir.name` only reaches the real
|
|
71
|
-
state_dir when the pipeline file sits at the workspace root -
|
|
72
|
+
state_dir when the pipeline file sits at the workspace root - the consumer runs
|
|
72
73
|
agentic-dev-loop/pipeline.yaml from the repo root, where `.pr` and
|
|
73
74
|
`agentic-dev-loop/.pr` diverge. Workspace-relative when under the workspace,
|
|
74
75
|
the absolute path when outside it."""
|
|
@@ -196,15 +197,41 @@ def _run_command(command: str, workspace: Path, timeout: float,
|
|
|
196
197
|
return None, out.decode("utf-8", "replace"), True
|
|
197
198
|
|
|
198
199
|
|
|
200
|
+
def _artifact_file(runtime: Runtime, step_name: str) -> str | None:
|
|
201
|
+
"""The declared artifact/produces filename a step writes, keyed by step
|
|
202
|
+
name - the artifacts dict only carries step name -> captured text."""
|
|
203
|
+
for step in runtime.cfg.steps:
|
|
204
|
+
if step.name == step_name:
|
|
205
|
+
if step.artifact is not None:
|
|
206
|
+
return step.artifact
|
|
207
|
+
if step.produces:
|
|
208
|
+
return step.produces[0]
|
|
209
|
+
return None
|
|
210
|
+
return None
|
|
211
|
+
|
|
212
|
+
|
|
199
213
|
def build_context(runtime: Runtime, docs: dict[str, str], subject: str,
|
|
200
|
-
notes: list[str] | None = None
|
|
214
|
+
notes: list[str] | None = None,
|
|
215
|
+
artifacts: dict[str, str] | None = None) -> str:
|
|
201
216
|
"""Assemble the attempt's shared message: subject, doc bundle, the
|
|
202
217
|
canonical dependency-policy paragraph (021), the expected-output-paths
|
|
203
218
|
section generated from step defs (a prompt merely naming files lets agents
|
|
204
|
-
skip them), and previous-attempt
|
|
219
|
+
skip them), accumulated stage artifacts (027), and previous-attempt
|
|
220
|
+
notes."""
|
|
205
221
|
parts = [f"You are working on {subject}.\n"]
|
|
206
222
|
for name, content in docs.items():
|
|
207
223
|
parts.append(f"## {name}\n\n{content}")
|
|
224
|
+
if artifacts:
|
|
225
|
+
parts.append("## Stage artifacts")
|
|
226
|
+
for step_name, content in artifacts.items():
|
|
227
|
+
capped = content[:ARTIFACT_CONTEXT_CAP]
|
|
228
|
+
if len(content) > ARTIFACT_CONTEXT_CAP:
|
|
229
|
+
filename = _artifact_file(runtime, step_name)
|
|
230
|
+
path = (f"{prompt_dir(runtime)}/{filename}" if filename
|
|
231
|
+
else prompt_dir(runtime))
|
|
232
|
+
capped += (f"\n\n[... content truncated - full text at "
|
|
233
|
+
f"`{path}`]")
|
|
234
|
+
parts.append(f"### {step_name}\n\n{capped}")
|
|
208
235
|
parts.append("## Dependency policy\n\n" + policy_injection(runtime.cfg))
|
|
209
236
|
lines = []
|
|
210
237
|
for step in runtime.cfg.steps:
|
|
@@ -240,7 +267,7 @@ def clear_attempt_artifacts(runtime: Runtime) -> None:
|
|
|
240
267
|
|
|
241
268
|
def _unclear_target(runtime: Runtime, step: StepConfig) -> str:
|
|
242
269
|
"""Where the unclear-verdict retry loops to: on_fail's goto when that is
|
|
243
|
-
a step, else the entry step (
|
|
270
|
+
a step, else the entry step (the consumer always looped back to dev)."""
|
|
244
271
|
target = route_target(step.on_fail)
|
|
245
272
|
if target is not None and target not in TERMINALS:
|
|
246
273
|
return target
|
|
@@ -295,8 +322,11 @@ def make_agent_node(runtime: Runtime, step: StepConfig):
|
|
|
295
322
|
attempt = state["attempt"]
|
|
296
323
|
subject = state["subject"]
|
|
297
324
|
max_iterations = step.max_iterations or DEFAULT_MAX_ITERATIONS
|
|
325
|
+
message = build_context(runtime, state["docs"], state["subject"],
|
|
326
|
+
state["notes"],
|
|
327
|
+
artifacts=state.get("artifacts", {}))
|
|
298
328
|
print(f"[{subject}] Running {step.agent} agent...", flush=True)
|
|
299
|
-
result = run_agent(step.agent,
|
|
329
|
+
result = run_agent(step.agent, message, runtime,
|
|
300
330
|
max_iterations=max_iterations, step=step)
|
|
301
331
|
if not result.ok:
|
|
302
332
|
print(f"[{subject}] {step.name} agent failed on attempt {attempt}.", flush=True)
|
|
@@ -330,7 +360,10 @@ def make_agent_node(runtime: Runtime, step: StepConfig):
|
|
|
330
360
|
if verdicts_map is None:
|
|
331
361
|
merged = {**state["step_verdicts"], step.name: "pass"} if captures else \
|
|
332
362
|
state["step_verdicts"]
|
|
333
|
-
|
|
363
|
+
update = {"step_verdicts": merged, "failed": False}
|
|
364
|
+
if captures and result.response is not None:
|
|
365
|
+
update["artifacts"] = {step.name: result.response}
|
|
366
|
+
return update
|
|
334
367
|
|
|
335
368
|
parsed = Verdict.from_text(result.response or "", verdicts_map)
|
|
336
369
|
merged = {**state["step_verdicts"], step.name: None}
|
|
@@ -340,16 +373,24 @@ def make_agent_node(runtime: Runtime, step: StepConfig):
|
|
|
340
373
|
if verdict == "pass":
|
|
341
374
|
print(f"[{subject}] {step.name} VERDICT: PASS on attempt {attempt}.",
|
|
342
375
|
flush=True)
|
|
343
|
-
|
|
376
|
+
update = {"step_verdicts": merged, "failed": False}
|
|
377
|
+
if captures and result.response is not None:
|
|
378
|
+
update["artifacts"] = {step.name: result.response}
|
|
379
|
+
return update
|
|
344
380
|
if verdict == "unverifiable":
|
|
345
381
|
print(f"[{subject}] {step.name} qa verdict: UNVERIFIABLE "
|
|
346
382
|
f"(moves on).", flush=True)
|
|
347
|
-
|
|
383
|
+
update = {"step_verdicts": merged, "failed": False}
|
|
384
|
+
if captures and result.response is not None:
|
|
385
|
+
update["artifacts"] = {step.name: result.response}
|
|
386
|
+
return update
|
|
348
387
|
print(f"[{subject}] {step.name} verdict: fail on attempt {attempt}.",
|
|
349
388
|
flush=True)
|
|
350
389
|
update = {"step_verdicts": merged,
|
|
351
390
|
"notes": [f"## {step.name} output (attempt {attempt})\n\n"
|
|
352
391
|
f"{result.response}"]}
|
|
392
|
+
if captures and result.response is not None:
|
|
393
|
+
update["artifacts"] = {step.name: result.response}
|
|
353
394
|
if isinstance(step.on_fail, Route) and step.on_fail.retry:
|
|
354
395
|
update["retry_target"] = step.on_fail.goto
|
|
355
396
|
return update
|
|
@@ -548,16 +589,14 @@ def make_router(runtime: Runtime, step: StepConfig):
|
|
|
548
589
|
|
|
549
590
|
def make_bump_node(runtime: Runtime):
|
|
550
591
|
"""Close the retry cycle: consume one attempt, clear per-attempt
|
|
551
|
-
artifacts, and
|
|
552
|
-
|
|
592
|
+
artifacts, and reset verdicts/failed for the next attempt. The retried
|
|
593
|
+
agent's message is rebuilt per run by make_agent_node from the
|
|
594
|
+
accumulated notes and artifacts (027)."""
|
|
553
595
|
|
|
554
596
|
def node(state: dict) -> dict:
|
|
555
597
|
clear_attempt_artifacts(runtime)
|
|
556
|
-
context = build_context(runtime, state["docs"], state["subject"],
|
|
557
|
-
state["notes"])
|
|
558
598
|
return {"attempt": state["attempt"] + 1,
|
|
559
599
|
"step_verdicts": {},
|
|
560
|
-
"context": context,
|
|
561
600
|
"failed": False}
|
|
562
601
|
|
|
563
602
|
return node
|
{agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentic-dev-loop
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.0a17
|
|
4
4
|
Summary: Turn an LLM into a gated, sandboxed, resumable construction loop over your codebase.
|
|
5
5
|
Author: Luke Wilson
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -231,7 +231,7 @@ secrets: # refs only - never values
|
|
|
231
231
|
key: KINDIE_CLIENT_SECRET # provider-specific locator
|
|
232
232
|
r2-keys:
|
|
233
233
|
from: env
|
|
234
|
-
key:
|
|
234
|
+
key: R2_KEYS
|
|
235
235
|
|
|
236
236
|
steps:
|
|
237
237
|
- name: qa
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Action tests - ported from
|
|
1
|
+
"""Action tests - ported from the consumer's TestKillListenersOnPort /
|
|
2
2
|
TestGitWorktreeClean / TestCommitNode, retargeted at the node-shaped
|
|
3
3
|
`(state, params) -> dict` API. Real processes and real git repos throughout:
|
|
4
4
|
git and sockets are local utilities, not network boundaries."""
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""load_agent tests - ported from
|
|
1
|
+
"""load_agent tests - ported from the consumer's TestLoadAgent, retargeted at the
|
|
2
2
|
library signature which also builds the Agent via the SDK factory. Real
|
|
3
3
|
agent files on disk (fail-fast file policy); real LLM/Agent SDK objects
|
|
4
4
|
(constructed locally, no network)."""
|
|
@@ -6,14 +6,14 @@ from pydantic import ValidationError
|
|
|
6
6
|
|
|
7
7
|
from agentic_dev_loop.core.config import Route, default_state_dir, load_config
|
|
8
8
|
|
|
9
|
-
# The plan's
|
|
9
|
+
# The plan's reference pipeline (consumer-equivalent) (009, YAML schema section),
|
|
10
10
|
# verbatim except for two substitutions: 012 replaced the agent step named
|
|
11
11
|
# `test` (artifact ci-fix.md, PASS/FAIL) with the mechanical command step
|
|
12
12
|
# named `test` - same position, same on_fail wiring; and 009 dropped the
|
|
13
13
|
# port_sweep step (its sweep moved into engine startup, no longer a pipeline
|
|
14
14
|
# step). This is the canonical pin: the documented schema must load.
|
|
15
15
|
FULL_YAML = """\
|
|
16
|
-
name:
|
|
16
|
+
name: reference-construction
|
|
17
17
|
|
|
18
18
|
extensions:
|
|
19
19
|
- agentic_dev_loop.engine.extensions.aidlc:AidlcExtension
|
|
@@ -86,7 +86,7 @@ def test_full_schema_example_round_trips(tmp_path):
|
|
|
86
86
|
p.write_text(FULL_YAML)
|
|
87
87
|
cfg = load_config(p)
|
|
88
88
|
|
|
89
|
-
assert cfg.name == "
|
|
89
|
+
assert cfg.name == "reference-construction"
|
|
90
90
|
assert cfg.extensions == ["agentic_dev_loop.engine.extensions.aidlc:AidlcExtension"]
|
|
91
91
|
assert cfg.additional_files == ["coding-standards-ddd.yaml"]
|
|
92
92
|
assert cfg.state_dir == ".pr"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Graph builder + run_pipeline behavioral tests.
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
The consumer-equivalent YAML below must reproduce the hardcoded loop's behavior:
|
|
4
4
|
dev → check → review → qa → commit, retries to dev, failure on
|
|
5
5
|
exhaustion/exception. run_agent is stubbed (the SDK network boundary);
|
|
6
6
|
everything else - files, git, checkpoints - is real."""
|
|
@@ -21,8 +21,8 @@ from agentic_dev_loop.engine.registry import Registry, default_registry, load_ex
|
|
|
21
21
|
from agentic_dev_loop.engine.stages import Runtime
|
|
22
22
|
from agentic_dev_loop.viz import VizBus
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
name:
|
|
24
|
+
REFERENCE_YAML = """\
|
|
25
|
+
name: reference-construction
|
|
26
26
|
extensions:
|
|
27
27
|
- agentic_dev_loop.engine.extensions.aidlc:AidlcExtension
|
|
28
28
|
additional_files:
|
|
@@ -101,10 +101,10 @@ def git(repo: Path, *args: str) -> str:
|
|
|
101
101
|
|
|
102
102
|
@pytest.fixture
|
|
103
103
|
def repo(tmp_path, monkeypatch, fake_sandbox):
|
|
104
|
-
"""A real git repo with the
|
|
104
|
+
"""A real git repo with the consumer-equivalent pipeline committed clean."""
|
|
105
105
|
repo = tmp_path / "repo"
|
|
106
106
|
repo.mkdir()
|
|
107
|
-
(repo / "pipeline.yaml").write_text(
|
|
107
|
+
(repo / "pipeline.yaml").write_text(REFERENCE_YAML)
|
|
108
108
|
(repo / "coding-standards-ddd.yaml").write_text("STANDARDS")
|
|
109
109
|
(repo / "plan.md").write_text("THE PLAN")
|
|
110
110
|
git(repo, "init")
|
|
@@ -131,7 +131,7 @@ def script_all_pass(fake_agents):
|
|
|
131
131
|
|
|
132
132
|
|
|
133
133
|
class TestTopology:
|
|
134
|
-
def
|
|
134
|
+
def test_reference_pipeline_node_set(self, repo):
|
|
135
135
|
cfg = load_config(Path("pipeline.yaml"))
|
|
136
136
|
registry = default_registry()
|
|
137
137
|
load_extensions(cfg.extensions, registry)
|
|
@@ -143,7 +143,7 @@ class TestTopology:
|
|
|
143
143
|
"bump", "success"} <= nodes
|
|
144
144
|
|
|
145
145
|
def test_unknown_action_fails_at_build(self, repo):
|
|
146
|
-
(repo / "bad.yaml").write_text(
|
|
146
|
+
(repo / "bad.yaml").write_text(REFERENCE_YAML.replace(
|
|
147
147
|
"action: commit", "action: no_such_action"))
|
|
148
148
|
cfg = load_config(Path("bad.yaml"))
|
|
149
149
|
runtime = Runtime(cfg=cfg, registry=default_registry(),
|
|
@@ -178,6 +178,31 @@ class TestRunPipeline:
|
|
|
178
178
|
second_dev = fake_agents.messages_for("dev")[1]
|
|
179
179
|
assert "broken build" in second_dev # fail note reached the next attempt
|
|
180
180
|
|
|
181
|
+
def test_downstream_agent_sees_prior_artifact(self, repo, fake_agents):
|
|
182
|
+
script_all_pass(fake_agents)
|
|
183
|
+
ok, attempts = run_pipeline("pipeline.yaml", args())
|
|
184
|
+
assert (ok, attempts) == (True, 1)
|
|
185
|
+
(dev_msg,) = fake_agents.messages_for("dev")
|
|
186
|
+
(check_msg,) = fake_agents.messages_for("check")
|
|
187
|
+
assert "## Stage artifacts" not in dev_msg # runs before anything accumulates
|
|
188
|
+
assert "## Stage artifacts" in check_msg
|
|
189
|
+
assert "built it" in check_msg # dev's captured response reached check
|
|
190
|
+
|
|
191
|
+
def test_retry_injects_review_artifact_notes_and_prior_dev(self, repo,
|
|
192
|
+
fake_agents):
|
|
193
|
+
script_all_pass(fake_agents)
|
|
194
|
+
fake_agents.set("review", [
|
|
195
|
+
("write", "review-findings.md", "VERDICT: NEEDS CHANGES\nfix the thing"),
|
|
196
|
+
("write", "review-findings.md", "VERDICT: APPROVED"),
|
|
197
|
+
])
|
|
198
|
+
ok, attempts = run_pipeline("pipeline.yaml", args())
|
|
199
|
+
assert (ok, attempts) == (True, 2)
|
|
200
|
+
second_dev = fake_agents.messages_for("dev")[1]
|
|
201
|
+
assert "## Stage artifacts" in second_dev
|
|
202
|
+
assert "built it" in second_dev # dev's own attempt-1 artifact
|
|
203
|
+
assert "fix the thing" in second_dev # review artifact + fail note
|
|
204
|
+
assert "## Previous attempt feedback" in second_dev
|
|
205
|
+
|
|
181
206
|
def test_exhaustion_fails(self, repo, fake_agents):
|
|
182
207
|
script_all_pass(fake_agents)
|
|
183
208
|
fake_agents.set("check", [("write", "ci-fix.md", "VERDICT: FAIL")])
|
|
@@ -248,7 +273,7 @@ class TestRunPipeline:
|
|
|
248
273
|
# is not a tooling modification - the loader's missing-file guard is
|
|
249
274
|
# what catches it.
|
|
250
275
|
(repo / "pipeline.yaml").write_text(
|
|
251
|
-
|
|
276
|
+
REFERENCE_YAML.replace("coding-standards-ddd.yaml",
|
|
252
277
|
"no-such-shared-file.yaml"))
|
|
253
278
|
git(repo, "add", "-A")
|
|
254
279
|
git(repo, "commit", "-m", "reference missing additional file")
|
|
@@ -257,7 +282,7 @@ class TestRunPipeline:
|
|
|
257
282
|
assert fake_agents.calls == []
|
|
258
283
|
|
|
259
284
|
def test_empty_loader_docs_fail_fast(self, repo, fake_agents):
|
|
260
|
-
(repo / "empty.yaml").write_text(
|
|
285
|
+
(repo / "empty.yaml").write_text(REFERENCE_YAML.replace(
|
|
261
286
|
"agentic_dev_loop.engine.extensions.aidlc:AidlcExtension",
|
|
262
287
|
"sample_ext:EmptyLoaderExtension"))
|
|
263
288
|
git(repo, "add", "-A")
|
|
@@ -267,7 +292,7 @@ class TestRunPipeline:
|
|
|
267
292
|
assert fake_agents.calls == []
|
|
268
293
|
|
|
269
294
|
|
|
270
|
-
LOCKED_YAML =
|
|
295
|
+
LOCKED_YAML = REFERENCE_YAML.replace(
|
|
271
296
|
"deps_policy: open\n",
|
|
272
297
|
"deps_policy: locked\nlockfiles: [uv.lock]\n",
|
|
273
298
|
1)
|
|
@@ -27,3 +27,11 @@ def test_unverifiable_verdict_rules_present():
|
|
|
27
27
|
|
|
28
28
|
def test_inconclusive_budget_rule_retained():
|
|
29
29
|
assert "INCONCLUSIVE" in _text()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_probe_plan_rules_present():
|
|
33
|
+
text = _text()
|
|
34
|
+
assert "**Variant matrix.**" in text
|
|
35
|
+
assert "**Outcome probes.**" in text
|
|
36
|
+
assert "**Concurrency probes.**" in text
|
|
37
|
+
assert "**Error messages are assertions.**" in text
|
|
@@ -38,7 +38,7 @@ def _cfg_with_secrets(tmp_path) -> PipelineConfig:
|
|
|
38
38
|
data = _base()
|
|
39
39
|
data["secrets"] = {
|
|
40
40
|
"kinde-client-secret": {"from": "env", "key": "KINDIE_CLIENT_SECRET"},
|
|
41
|
-
"r2-keys": {"from": "env", "key": "
|
|
41
|
+
"r2-keys": {"from": "env", "key": "R2_KEYS"},
|
|
42
42
|
}
|
|
43
43
|
data["steps"] = [
|
|
44
44
|
{"name": "qa", "agent": "qa", "secrets": ["kinde-client-secret"],
|
|
@@ -127,7 +127,7 @@ class TestProviderResolution:
|
|
|
127
127
|
def test_resolve_secrets_returns_ref_keyed_values(self, tmp_path):
|
|
128
128
|
cfg = _cfg_with_secrets(tmp_path)
|
|
129
129
|
resolved = resolve_secrets(cfg, {"KINDIE_CLIENT_SECRET": "kinde-secret",
|
|
130
|
-
"
|
|
130
|
+
"R2_KEYS": "r2-keys"})
|
|
131
131
|
assert resolved == {"kinde-client-secret": "kinde-secret",
|
|
132
132
|
"r2-keys": "r2-keys"}
|
|
133
133
|
|
|
@@ -137,14 +137,14 @@ class TestProviderResolution:
|
|
|
137
137
|
resolve_secrets(cfg, {"KINDIE_CLIENT_SECRET": "kinde-secret"})
|
|
138
138
|
msg = str(e.value)
|
|
139
139
|
assert "r2-keys" in msg
|
|
140
|
-
assert "
|
|
140
|
+
assert "R2_KEYS" in msg
|
|
141
141
|
assert "env" in msg
|
|
142
142
|
assert "['<none>']" in msg # r2-keys is declared but unused by any step
|
|
143
143
|
|
|
144
144
|
def test_resolve_secrets_missing_used_ref_names_step(self, tmp_path):
|
|
145
145
|
cfg = _cfg_with_secrets(tmp_path)
|
|
146
146
|
with pytest.raises(SecretResolutionError) as e:
|
|
147
|
-
resolve_secrets(cfg, {"
|
|
147
|
+
resolve_secrets(cfg, {"R2_KEYS": "r2-keys"})
|
|
148
148
|
assert "kinde-client-secret" in str(e.value)
|
|
149
149
|
assert "['qa']" in str(e.value)
|
|
150
150
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Stage nodes, routers, context assembly, and the run_agent port.
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
The consumer hardcoded stages (dev/test/review/qa + bump) become per-step node
|
|
4
4
|
and router closures built from the pipeline config; the fixed policies
|
|
5
5
|
(hygiene, verdict-first truncation, unclear-verdict retry, notes) stay here."""
|
|
6
6
|
|
|
@@ -17,8 +17,9 @@ from openhands.sdk.conversation.state import ConversationExecutionStatus
|
|
|
17
17
|
from agentic_dev_loop.core.config import PipelineConfig
|
|
18
18
|
from agentic_dev_loop.engine.registry import Registry, default_registry
|
|
19
19
|
from agentic_dev_loop.engine.stages import (
|
|
20
|
-
Runtime, _run_command, build_context,
|
|
21
|
-
|
|
20
|
+
ARTIFACT_CONTEXT_CAP, Runtime, _run_command, build_context,
|
|
21
|
+
make_action_node, make_agent_node, make_bump_node, make_command_node,
|
|
22
|
+
make_router, run_agent,
|
|
22
23
|
)
|
|
23
24
|
|
|
24
25
|
|
|
@@ -53,7 +54,7 @@ def runtime(tmp_path, fake_sandbox):
|
|
|
53
54
|
|
|
54
55
|
def make_state(**over):
|
|
55
56
|
state = {"subject": "plan plan.md", "docs": {"plan.md": "THE PLAN"},
|
|
56
|
-
"
|
|
57
|
+
"artifacts": {}, "attempt": 1, "max_attempts": 3, "notes": [],
|
|
57
58
|
"failed": False, "outcome": None, "commit_allowed": False,
|
|
58
59
|
"step_verdicts": {}, "retry_target": None, "thread_id": "t1"}
|
|
59
60
|
state.update(over)
|
|
@@ -78,7 +79,7 @@ class TestBuildContext:
|
|
|
78
79
|
assert "## Previous attempt feedback\n\nnote one\n\nnote two" in ctx
|
|
79
80
|
|
|
80
81
|
def test_output_paths_workspace_relative_for_subdirectory_pipeline(self, tmp_path):
|
|
81
|
-
"""
|
|
82
|
+
"""consumer layout: the pipeline YAML lives in a subdirectory while the
|
|
82
83
|
workspace is the repo root - the prompt path must reach the real
|
|
83
84
|
state_dir; state_dir.name alone sends agents to the wrong directory
|
|
84
85
|
(U18 burned 3 attempts on an unfindable ci-fix.md)."""
|
|
@@ -114,6 +115,27 @@ class TestBuildContext:
|
|
|
114
115
|
assert "dev agent writes:" in ctx
|
|
115
116
|
assert "test agent writes:" not in ctx
|
|
116
117
|
|
|
118
|
+
def test_artifacts_render_one_subsection_per_artifact_in_order(self, runtime):
|
|
119
|
+
ctx = build_context(runtime, {}, "s", [],
|
|
120
|
+
artifacts={"dev": "summary text",
|
|
121
|
+
"review": "findings text"})
|
|
122
|
+
assert "## Stage artifacts" in ctx
|
|
123
|
+
assert "### dev\n\nsummary text" in ctx
|
|
124
|
+
assert "### review\n\nfindings text" in ctx
|
|
125
|
+
assert ctx.index("### dev") < ctx.index("### review") # dict order
|
|
126
|
+
|
|
127
|
+
def test_artifact_content_capped_with_run_state_path_marker(self, runtime):
|
|
128
|
+
big = "x" * (ARTIFACT_CONTEXT_CAP + 100)
|
|
129
|
+
ctx = build_context(runtime, {}, "s", [], artifacts={"dev": big})
|
|
130
|
+
assert "x" * ARTIFACT_CONTEXT_CAP in ctx
|
|
131
|
+
assert "x" * (ARTIFACT_CONTEXT_CAP + 100) not in ctx
|
|
132
|
+
assert ("[... content truncated - full text at "
|
|
133
|
+
"`.pr/implementation-summary.md`]") in ctx
|
|
134
|
+
|
|
135
|
+
def test_no_artifacts_renders_no_section(self, runtime):
|
|
136
|
+
ctx = build_context(runtime, {}, "s", [])
|
|
137
|
+
assert "## Stage artifacts" not in ctx
|
|
138
|
+
|
|
117
139
|
|
|
118
140
|
class TestDepsPolicyContext:
|
|
119
141
|
def test_locked_policy_injected(self, runtime):
|
|
@@ -182,6 +204,40 @@ class TestAgentNode:
|
|
|
182
204
|
(note,) = update["notes"]
|
|
183
205
|
assert "broken build" in note and "attempt 2" in note
|
|
184
206
|
|
|
207
|
+
def test_no_verdicts_branch_captures_artifact(self, runtime, fake_agents):
|
|
208
|
+
fake_agents.set("dev", [("write", "implementation-summary.md", "MARKER-DEV")])
|
|
209
|
+
update = make_agent_node(runtime, runtime.cfg.steps[0])(make_state())
|
|
210
|
+
assert update["artifacts"] == {"dev": "MARKER-DEV"}
|
|
211
|
+
|
|
212
|
+
def test_pass_verdict_captures_artifact(self, runtime, fake_agents):
|
|
213
|
+
fake_agents.set("check", [("write", "ci-fix.md", "MARKER-CHECK\nVERDICT: PASS\n")])
|
|
214
|
+
update = make_agent_node(runtime, runtime.cfg.steps[1])(make_state())
|
|
215
|
+
assert update["artifacts"] == {"check": "MARKER-CHECK\nVERDICT: PASS\n"}
|
|
216
|
+
|
|
217
|
+
def test_fail_verdict_captures_artifact(self, runtime, fake_agents):
|
|
218
|
+
fake_agents.set("check", [("write", "ci-fix.md", "MARKER-FAIL\nVERDICT: FAIL\n")])
|
|
219
|
+
update = make_agent_node(runtime, runtime.cfg.steps[1])(make_state())
|
|
220
|
+
assert update["artifacts"] == {"check": "MARKER-FAIL\nVERDICT: FAIL\n"}
|
|
221
|
+
|
|
222
|
+
def test_unverifiable_verdict_captures_artifact(self, runtime, fake_agents):
|
|
223
|
+
step = make_cfg([
|
|
224
|
+
{"name": "qa", "agent": "qa", "artifact": "qa-report.md",
|
|
225
|
+
"verdicts": {"pass": "PASS", "fail": "FAIL",
|
|
226
|
+
"unverifiable": "UNVERIFIABLE"},
|
|
227
|
+
"on_pass": "success"}]).steps[0]
|
|
228
|
+
fake_agents.set("qa", [("write", "qa-report.md", "MARKER-UNVER\nVERDICT: UNVERIFIABLE\n")])
|
|
229
|
+
update = make_agent_node(runtime, step)(make_state())
|
|
230
|
+
assert update["artifacts"] == {"qa": "MARKER-UNVER\nVERDICT: UNVERIFIABLE\n"}
|
|
231
|
+
|
|
232
|
+
def test_capture_error_branch_returns_no_artifacts(self, runtime, fake_agents):
|
|
233
|
+
update = make_agent_node(runtime, runtime.cfg.steps[1])(make_state())
|
|
234
|
+
assert "artifacts" not in update
|
|
235
|
+
|
|
236
|
+
def test_unclear_branch_returns_no_artifacts(self, runtime, fake_agents):
|
|
237
|
+
fake_agents.set("check", [("write", "ci-fix.md", "undecided")])
|
|
238
|
+
update = make_agent_node(runtime, runtime.cfg.steps[1])(make_state())
|
|
239
|
+
assert "artifacts" not in update
|
|
240
|
+
|
|
185
241
|
def test_verdicts_merge_with_prior_steps(self, runtime, fake_agents):
|
|
186
242
|
fake_agents.set("check", [("write", "ci-fix.md", "VERDICT: PASS")])
|
|
187
243
|
state = make_state(step_verdicts={"dev": "pass"})
|
|
@@ -528,14 +584,11 @@ class TestBumpNode:
|
|
|
528
584
|
def test_consumes_attempt_resets_verdicts_rebuilds_cleans(self, runtime):
|
|
529
585
|
for stale in ("ci-fix.md", "implementation-summary.md", "last-error.md"):
|
|
530
586
|
(runtime.state_dir / stale).write_text("stale")
|
|
531
|
-
state = make_state(attempt=1, notes=["N1"],
|
|
587
|
+
state = make_state(attempt=1, notes=["N1"], artifacts={"dev": "D1"},
|
|
532
588
|
step_verdicts={"check": "fail"})
|
|
533
589
|
update = make_bump_node(runtime)(state)
|
|
534
|
-
assert update
|
|
535
|
-
assert
|
|
536
|
-
assert update["failed"] is False
|
|
537
|
-
assert "## Previous attempt feedback\n\nN1" in update["context"]
|
|
538
|
-
assert "## plan.md\n\nTHE PLAN" in update["context"]
|
|
590
|
+
assert update == {"attempt": 2, "step_verdicts": {}, "failed": False}
|
|
591
|
+
assert "context" not in update # message is rebuilt per run (027)
|
|
539
592
|
for stale in ("ci-fix.md", "implementation-summary.md", "last-error.md"):
|
|
540
593
|
assert not (runtime.state_dir / stale).exists()
|
|
541
594
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/core/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/core/results.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/core/verdict.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/__init__.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/agents.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/dryrun.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/isolation.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/loaders.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/outputs.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/registry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/engine/secrets.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/viz/__init__.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop/viz/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/src/agentic_dev_loop.egg-info/requires.txt
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/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
|
{agentic_dev_loop-1.0.0a16 → agentic_dev_loop-1.0.0a17}/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
|