agentic-dev-loop 1.0.0a4__tar.gz → 1.0.0a5__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.0a4 → agentic_dev_loop-1.0.0a5}/PKG-INFO +1 -1
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/pyproject.toml +1 -1
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/cli.py +21 -8
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/dryrun.py +10 -4
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/graph.py +11 -4
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop.egg-info/PKG-INFO +1 -1
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_cli.py +21 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_dry_run.py +11 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_graph.py +13 -1
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/LICENSE +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/README.md +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/setup.cfg +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/actions.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/agents.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/config.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/extensions/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/extensions/aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/isolation.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/llm.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/loaders.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/outputs.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/proc.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/py.typed +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/registry.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/results.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/stages.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/viz.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop.egg-info/SOURCES.txt +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_actions.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_agents.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_config.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_e2e_fixtures.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_isolation.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_isolation_integration.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_isolation_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_llm.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_loaders.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_outputs.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_outputs_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_proc.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_publish.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_registry.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_results.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_sdk_compat.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_source_resolution.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_stages.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_verdict.py +0 -0
- {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/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.0a5"
|
|
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"
|
|
@@ -9,10 +9,10 @@ import importlib.metadata
|
|
|
9
9
|
import sys
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
-
from .config import load_config
|
|
12
|
+
from .config import PipelineConfig, load_config
|
|
13
13
|
from .dryrun import dry_run
|
|
14
14
|
from .isolation import clean_stamps, isolation_state_dir
|
|
15
|
-
from .registry import default_registry, load_extensions
|
|
15
|
+
from .registry import Registry, default_registry, load_extensions
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def _version() -> str:
|
|
@@ -63,11 +63,21 @@ def _core_parser() -> argparse.ArgumentParser:
|
|
|
63
63
|
return parser
|
|
64
64
|
|
|
65
65
|
|
|
66
|
-
def
|
|
67
|
-
"""
|
|
66
|
+
def _registry_for(cfg_path: Path) -> tuple[Registry, PipelineConfig]:
|
|
67
|
+
"""Load the pipeline config and build ONE registry with its extensions.
|
|
68
|
+
The CLI loads extensions exactly once per process: the SDK tool registry
|
|
69
|
+
is process-global and BR-3 rejects a second registration of any tool
|
|
70
|
+
name, so the registry built here is passed down to the dry run and the
|
|
71
|
+
run path instead of being rebuilt."""
|
|
68
72
|
cfg = load_config(cfg_path)
|
|
69
73
|
registry = default_registry()
|
|
70
74
|
load_extensions(cfg.extensions, registry)
|
|
75
|
+
return registry, cfg
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def build_parser(cfg_path) -> argparse.ArgumentParser:
|
|
79
|
+
"""The full parser for one pipeline: core flags + loader-contributed."""
|
|
80
|
+
registry, _ = _registry_for(cfg_path)
|
|
71
81
|
parser = _core_parser()
|
|
72
82
|
for loader in registry.document_loaders:
|
|
73
83
|
loader.add_cli_args(parser)
|
|
@@ -84,20 +94,23 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
84
94
|
# handles --help and the missing-positional error itself (both exit).
|
|
85
95
|
_core_parser().parse_args(argv)
|
|
86
96
|
return 2 # unreachable — parse_args exits first
|
|
87
|
-
|
|
97
|
+
registry, cfg = _registry_for(known.pipeline)
|
|
98
|
+
parser = _core_parser()
|
|
99
|
+
for loader in registry.document_loaders:
|
|
100
|
+
loader.add_cli_args(parser)
|
|
101
|
+
args = parser.parse_args(argv)
|
|
88
102
|
if args.clean_stamps:
|
|
89
|
-
cfg = load_config(args.pipeline)
|
|
90
103
|
state = isolation_state_dir(cfg)
|
|
91
104
|
removed = clean_stamps(state)
|
|
92
105
|
print(f"Removed {removed} cached provision stamp(s) from "
|
|
93
106
|
f"{state / 'stamps'}", flush=True)
|
|
94
107
|
return 0
|
|
95
108
|
if args.dry_run:
|
|
96
|
-
return dry_run(args.pipeline, args)
|
|
109
|
+
return dry_run(args.pipeline, args, registry)
|
|
97
110
|
# The run path alone needs the heavy graph/SDK imports — defer so a
|
|
98
111
|
# dry run, --clean-stamps, and --help stay side-effect free.
|
|
99
112
|
from .graph import run_pipeline
|
|
100
|
-
success, _attempts = run_pipeline(args.pipeline, args)
|
|
113
|
+
success, _attempts = run_pipeline(args.pipeline, args, registry=registry)
|
|
101
114
|
if not success:
|
|
102
115
|
print("agentic-dev-loop: pipeline failed — exiting with code 1",
|
|
103
116
|
file=sys.stderr)
|
|
@@ -101,16 +101,22 @@ def _print_summary(cfg_path: Path, cfg: PipelineConfig, subject: str,
|
|
|
101
101
|
print("dry run complete — nothing executed")
|
|
102
102
|
|
|
103
103
|
|
|
104
|
-
def dry_run(cfg_path, args) -> int:
|
|
104
|
+
def dry_run(cfg_path, args, registry: Registry | None = None) -> int:
|
|
105
105
|
"""Validate a pipeline end-to-end without executing it, and print a
|
|
106
106
|
summary plan. Returns 0 on a clean load; any load failure propagates as
|
|
107
|
-
the existing loud error (the CLI exits 1).
|
|
107
|
+
the existing loud error (the CLI exits 1).
|
|
108
|
+
|
|
109
|
+
`registry` carries extensions already loaded by the CLI (build_parser
|
|
110
|
+
loads them once for loader-contributed flags; a second load would
|
|
111
|
+
collide with the SDK's process-global tool registry). When absent —
|
|
112
|
+
programmatic calls — the registry is built here."""
|
|
108
113
|
cfg_path = Path(cfg_path)
|
|
109
114
|
cfg = load_config(cfg_path)
|
|
110
115
|
base = cfg_path.resolve().parent
|
|
111
116
|
|
|
112
|
-
registry
|
|
113
|
-
|
|
117
|
+
if registry is None:
|
|
118
|
+
registry = default_registry()
|
|
119
|
+
load_extensions(cfg.extensions, registry)
|
|
114
120
|
|
|
115
121
|
_validate_agents(cfg, base / cfg.agents_dir)
|
|
116
122
|
_validate_actions(cfg, registry)
|
|
@@ -80,7 +80,8 @@ def build_graph(runtime: Runtime, checkpointer=None):
|
|
|
80
80
|
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
def run_pipeline(cfg_path, args, *, viz_bus=None
|
|
83
|
+
def run_pipeline(cfg_path, args, *, viz_bus=None,
|
|
84
|
+
registry: Registry | None = None) -> tuple[bool, int]:
|
|
84
85
|
"""Load a pipeline YAML, resolve docs via the selected document loader,
|
|
85
86
|
and run the graph to completion. Returns (success, attempts) — the
|
|
86
87
|
public API, yosk's `construct`/`resume_loop` equivalent.
|
|
@@ -95,14 +96,20 @@ def run_pipeline(cfg_path, args, *, viz_bus=None) -> tuple[bool, int]:
|
|
|
95
96
|
events; the CLI path additionally serves the HTTP page and opens the
|
|
96
97
|
browser, and the process exits as soon as the loop completes.
|
|
97
98
|
Programmatic Namespaces without a ``no_viz`` attribute get no server —
|
|
98
|
-
default-on is a CLI-layer concern.
|
|
99
|
+
default-on is a CLI-layer concern.
|
|
100
|
+
|
|
101
|
+
``registry`` carries extensions already loaded by the CLI (it loads them
|
|
102
|
+
once, for loader-contributed flags; a second load would collide with the
|
|
103
|
+
SDK's process-global tool registry). When absent — programmatic calls —
|
|
104
|
+
the registry is built here."""
|
|
99
105
|
cfg_path = Path(cfg_path)
|
|
100
106
|
cfg = load_config(cfg_path)
|
|
101
107
|
base = cfg_path.resolve().parent
|
|
102
108
|
max_attempts = getattr(args, "max_attempts", None) or cfg.max_attempts
|
|
103
109
|
|
|
104
|
-
registry
|
|
105
|
-
|
|
110
|
+
if registry is None:
|
|
111
|
+
registry = default_registry()
|
|
112
|
+
load_extensions(cfg.extensions, registry)
|
|
106
113
|
|
|
107
114
|
state_dir = Path(cfg.state_dir).expanduser()
|
|
108
115
|
if not state_dir.is_absolute():
|
|
@@ -361,3 +361,24 @@ class TestRefreshAndCleanStamps:
|
|
|
361
361
|
assert main(["clean.yaml", "--clean-stamps"]) == 0
|
|
362
362
|
assert not (state / "stamps" / "k1").exists()
|
|
363
363
|
assert not (state / "stamps" / "k2").exists()
|
|
364
|
+
|
|
365
|
+
def test_clean_stamps_loads_config_once(self, repo, tmp_path, monkeypatch):
|
|
366
|
+
# --clean-stamps must reuse the config _registry_for already loaded;
|
|
367
|
+
# loading it twice is the pre-fix behaviour this guards against.
|
|
368
|
+
import agentic_dev_loop.cli as cli_mod
|
|
369
|
+
|
|
370
|
+
state = tmp_path / "iso-state"
|
|
371
|
+
yaml = CLEAN_YAML.format(iso_state_dir=str(state))
|
|
372
|
+
write_pipeline(repo, "clean.yaml", yaml)
|
|
373
|
+
|
|
374
|
+
real = cli_mod.load_config
|
|
375
|
+
calls = []
|
|
376
|
+
|
|
377
|
+
def counting(cfg_path):
|
|
378
|
+
calls.append(cfg_path)
|
|
379
|
+
return real(cfg_path)
|
|
380
|
+
|
|
381
|
+
monkeypatch.setattr(cli_mod, "load_config", counting)
|
|
382
|
+
|
|
383
|
+
assert cli_mod.main(["clean.yaml", "--clean-stamps"]) == 0
|
|
384
|
+
assert len(calls) == 1
|
|
@@ -111,6 +111,17 @@ class TestDryRun:
|
|
|
111
111
|
assert "test [command]" in out
|
|
112
112
|
assert "network=True" in out
|
|
113
113
|
|
|
114
|
+
def test_tool_extension_loads_once(self, repo, capsys):
|
|
115
|
+
"""A tool-registering extension must survive the CLI's two-phase load:
|
|
116
|
+
build_parser loads extensions once for loader-contributed flags, and
|
|
117
|
+
the dry run reuses that registry instead of loading again (a second
|
|
118
|
+
load collides with the SDK's process-global tool registry and BR-3
|
|
119
|
+
rejects it — the pre-fix double-load crash)."""
|
|
120
|
+
(repo / "tool.yaml").write_text(
|
|
121
|
+
"extensions:\n - sample_ext:DryRunProbeExtension\n" + DRY_RUN_YAML)
|
|
122
|
+
assert main(["tool.yaml", "--dry-run", "--plan", "plan.md"]) == 0
|
|
123
|
+
assert "dry run complete" in capsys.readouterr().out
|
|
124
|
+
|
|
114
125
|
|
|
115
126
|
class TestDryRunHelp:
|
|
116
127
|
def test_help_documents_flag(self, repo, capsys):
|
|
@@ -17,7 +17,7 @@ import pytest
|
|
|
17
17
|
from agentic_dev_loop.config import load_config
|
|
18
18
|
from agentic_dev_loop.graph import build_graph, run_pipeline
|
|
19
19
|
from agentic_dev_loop.llm import build_llm
|
|
20
|
-
from agentic_dev_loop.registry import default_registry, load_extensions
|
|
20
|
+
from agentic_dev_loop.registry import Registry, default_registry, load_extensions
|
|
21
21
|
from agentic_dev_loop.stages import Runtime
|
|
22
22
|
from agentic_dev_loop.viz import VizBus
|
|
23
23
|
|
|
@@ -483,3 +483,15 @@ class TestSandboxLifecycle:
|
|
|
483
483
|
with pytest.raises(KeyboardInterrupt):
|
|
484
484
|
run_pipeline("pipeline.yaml", args())
|
|
485
485
|
assert fake_sandbox.closed
|
|
486
|
+
|
|
487
|
+
def test_run_pipeline_registry_param_typed():
|
|
488
|
+
"""API-consistency guard: run_pipeline's registry kwarg must carry the
|
|
489
|
+
same Registry | None annotation as dry_run's — an untyped param is how
|
|
490
|
+
this drifted once already."""
|
|
491
|
+
import inspect
|
|
492
|
+
from typing import get_type_hints
|
|
493
|
+
|
|
494
|
+
sig = inspect.signature(run_pipeline)
|
|
495
|
+
ann = sig.parameters["registry"].annotation
|
|
496
|
+
assert ann is not inspect.Parameter.empty
|
|
497
|
+
assert get_type_hints(run_pipeline)["registry"] == Registry | None
|
|
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.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/extensions/__init__.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/extensions/aidlc.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
|
{agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop.egg-info/requires.txt
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/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
|