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.
Files changed (54) hide show
  1. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/PKG-INFO +1 -1
  2. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/pyproject.toml +1 -1
  3. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/cli.py +21 -8
  4. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/dryrun.py +10 -4
  5. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/graph.py +11 -4
  6. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop.egg-info/PKG-INFO +1 -1
  7. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_cli.py +21 -0
  8. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_dry_run.py +11 -0
  9. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_graph.py +13 -1
  10. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/LICENSE +0 -0
  11. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/README.md +0 -0
  12. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/setup.cfg +0 -0
  13. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/__init__.py +0 -0
  14. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/actions.py +0 -0
  15. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/agents.py +0 -0
  16. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/config.py +0 -0
  17. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/extensions/__init__.py +0 -0
  18. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/extensions/aidlc.py +0 -0
  19. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/isolation.py +0 -0
  20. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/llm.py +0 -0
  21. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/loaders.py +0 -0
  22. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/outputs.py +0 -0
  23. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/proc.py +0 -0
  24. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/py.typed +0 -0
  25. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/registry.py +0 -0
  26. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/results.py +0 -0
  27. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/stages.py +0 -0
  28. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop/viz.py +0 -0
  29. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop.egg-info/SOURCES.txt +0 -0
  30. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
  31. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
  32. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
  33. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
  34. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_actions.py +0 -0
  35. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_agents.py +0 -0
  36. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_aidlc.py +0 -0
  37. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_config.py +0 -0
  38. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_e2e_fixtures.py +0 -0
  39. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_isolation.py +0 -0
  40. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_isolation_integration.py +0 -0
  41. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_isolation_pbt.py +0 -0
  42. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_llm.py +0 -0
  43. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_loaders.py +0 -0
  44. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_outputs.py +0 -0
  45. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_outputs_pbt.py +0 -0
  46. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_proc.py +0 -0
  47. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_publish.py +0 -0
  48. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_registry.py +0 -0
  49. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_results.py +0 -0
  50. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_sdk_compat.py +0 -0
  51. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_source_resolution.py +0 -0
  52. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_stages.py +0 -0
  53. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/tests/test_verdict.py +0 -0
  54. {agentic_dev_loop-1.0.0a4 → agentic_dev_loop-1.0.0a5}/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.0a4
3
+ Version: 1.0.0a5
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agentic-dev-loop"
7
- version = "1.0.0a4"
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 build_parser(cfg_path) -> argparse.ArgumentParser:
67
- """The full parser for one pipeline: core flags + loader-contributed."""
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
- args = build_parser(known.pipeline).parse_args(argv)
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 = default_registry()
113
- load_extensions(cfg.extensions, registry)
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) -> tuple[bool, int]:
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 = default_registry()
105
- load_extensions(cfg.extensions, registry)
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():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-dev-loop
3
- Version: 1.0.0a4
3
+ Version: 1.0.0a5
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
@@ -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