agentic-dev-loop 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl
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/extensions/aidlc.py +43 -71
- {agentic_dev_loop-0.2.0.dist-info → agentic_dev_loop-0.3.0.dist-info}/METADATA +13 -11
- {agentic_dev_loop-0.2.0.dist-info → agentic_dev_loop-0.3.0.dist-info}/RECORD +7 -7
- {agentic_dev_loop-0.2.0.dist-info → agentic_dev_loop-0.3.0.dist-info}/WHEEL +0 -0
- {agentic_dev_loop-0.2.0.dist-info → agentic_dev_loop-0.3.0.dist-info}/entry_points.txt +0 -0
- {agentic_dev_loop-0.2.0.dist-info → agentic_dev_loop-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {agentic_dev_loop-0.2.0.dist-info → agentic_dev_loop-0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
"""AI-DLC document-loader extension. Loading IS choosing: name
|
|
2
2
|
`agentic_dev_loop.extensions.aidlc:AidlcExtension` in the pipeline's
|
|
3
|
-
`extensions:` list and the pipeline gains `--
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
`
|
|
3
|
+
`extensions:` list and the pipeline gains `--aidlc-unit` — the unit
|
|
4
|
+
directory name under `aidlc-docs/construction/` (e.g. `unit-3`,
|
|
5
|
+
`support-for-windows`), matched verbatim — plus bare-run autodetect of
|
|
6
|
+
the current unit from the Current Stage line of
|
|
7
|
+
`aidlc-docs/aidlc-state.md`.
|
|
8
|
+
|
|
9
|
+
No input mangling. AI-DLC's own ruleset imposes no naming convention on
|
|
10
|
+
unit directories (`{unit-name}` is a free placeholder), so the extension
|
|
11
|
+
imposes none either: the slug is the directory name, exactly. `U3`, `3`,
|
|
12
|
+
`004` and friends are NOT accepted unless a directory is literally named
|
|
13
|
+
that. The Current Stage line names the unit as its directory name
|
|
14
|
+
("Unit unit-3", "Unit support-for-windows") for autodetect.
|
|
11
15
|
"""
|
|
12
16
|
|
|
13
17
|
import argparse
|
|
@@ -15,59 +19,29 @@ import re
|
|
|
15
19
|
from pathlib import Path
|
|
16
20
|
|
|
17
21
|
_DOC_SUFFIXES = (".md", ".yaml", ".yml", ".txt")
|
|
18
|
-
_UNIT_DIR_NUMBER = re.compile(r"unit[-_](\d+)")
|
|
19
22
|
|
|
20
23
|
|
|
21
|
-
def
|
|
22
|
-
"""Leading digits of a unit id (``"004-preset-management"`` -> 4), or
|
|
23
|
-
None for ids that do not start with a number."""
|
|
24
|
-
match = re.match(r"(\d+)", text)
|
|
25
|
-
return int(match.group(1)) if match else None
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _dir_unit_number(dir_name: str) -> int | None:
|
|
29
|
-
"""Numeric prefix of a unit dir name (``"unit-004-preset-management"``
|
|
30
|
-
-> 4, ``"unit-14"`` -> 14)."""
|
|
31
|
-
match = _UNIT_DIR_NUMBER.match(dir_name.lower())
|
|
32
|
-
return int(match.group(1)) if match else None
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def resolve_unit_docs(docs_dir: Path | str, unit_id: str) -> dict[str, str]:
|
|
24
|
+
def resolve_unit_docs(docs_dir: Path | str, unit_slug: str) -> dict[str, str]:
|
|
36
25
|
docs: dict[str, str] = {}
|
|
37
26
|
docs_dir = Path(docs_dir)
|
|
38
|
-
|
|
39
|
-
if unit_id[:1] in ("U", "u"):
|
|
40
|
-
unit_id = unit_id[1:]
|
|
27
|
+
unit_slug = unit_slug.strip()
|
|
41
28
|
|
|
42
|
-
unit_patterns = [f"unit-{unit_id}", f"unit_{unit_id}"]
|
|
43
|
-
number = _leading_number(unit_id)
|
|
44
29
|
construction_dir = docs_dir / "construction"
|
|
45
|
-
|
|
30
|
+
unit_dir: Path | None = None
|
|
46
31
|
if construction_dir.is_dir():
|
|
47
32
|
for entry in sorted(construction_dir.iterdir()):
|
|
48
|
-
if
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if name == unit_patterns[0].lower() or name == unit_patterns[1].lower():
|
|
52
|
-
candidates.append(entry)
|
|
53
|
-
elif number is not None and _dir_unit_number(entry.name) == number:
|
|
54
|
-
candidates.append(entry)
|
|
55
|
-
|
|
56
|
-
candidates = sorted(set(candidates), key=lambda p: p.name.lower())
|
|
57
|
-
if len(candidates) > 1:
|
|
58
|
-
raise RuntimeError(
|
|
59
|
-
f"Ambiguous unit id '{unit_id}': multiple unit directories match "
|
|
60
|
-
f"({', '.join(p.name for p in candidates)}). Pass the full unit "
|
|
61
|
-
"slug to disambiguate.")
|
|
33
|
+
if entry.is_dir() and entry.name == unit_slug:
|
|
34
|
+
unit_dir = entry
|
|
35
|
+
break
|
|
62
36
|
|
|
63
|
-
unit_dir = candidates[0] if candidates else None
|
|
64
37
|
if unit_dir is None:
|
|
65
|
-
|
|
66
|
-
if
|
|
67
|
-
|
|
38
|
+
available = ""
|
|
39
|
+
if construction_dir.is_dir():
|
|
40
|
+
names = sorted(p.name for p in construction_dir.iterdir() if p.is_dir())
|
|
41
|
+
available = f" (available: {', '.join(names)})"
|
|
68
42
|
raise FileNotFoundError(
|
|
69
|
-
f"No
|
|
70
|
-
f"(
|
|
43
|
+
f"No unit directory named '{unit_slug}' under {construction_dir}"
|
|
44
|
+
f"{available or ' (no unit directories found)'}")
|
|
71
45
|
|
|
72
46
|
for file in sorted(unit_dir.rglob("*")):
|
|
73
47
|
if file.is_file() and file.suffix in _DOC_SUFFIXES:
|
|
@@ -94,37 +68,35 @@ def resolve_unit_docs(docs_dir: Path | str, unit_id: str) -> dict[str, str]:
|
|
|
94
68
|
# present is how the spec reaches agents.
|
|
95
69
|
plans_dir = docs_dir / "construction" / "plans"
|
|
96
70
|
if plans_dir.is_dir():
|
|
97
|
-
for file in sorted(plans_dir.glob(f"
|
|
71
|
+
for file in sorted(plans_dir.glob(f"{unit_slug}-*-code-generation-plan.md")):
|
|
98
72
|
docs[f"plans/{file.name}"] = file.read_text()
|
|
99
73
|
|
|
100
74
|
return docs
|
|
101
75
|
|
|
102
76
|
|
|
103
77
|
def get_current_unit(docs_dir: Path | str) -> str:
|
|
104
|
-
"""Read the current unit from the aidlc-state.md Current Stage line.
|
|
78
|
+
"""Read the current unit slug from the aidlc-state.md Current Stage line.
|
|
79
|
+
|
|
80
|
+
The stage line must name the unit as its directory name ("Unit unit-3",
|
|
81
|
+
"Unit support-for-windows"); the slug is returned verbatim for a direct
|
|
82
|
+
directory match.
|
|
83
|
+
"""
|
|
105
84
|
state_file = Path(docs_dir) / "aidlc-state.md"
|
|
106
85
|
if not state_file.is_file():
|
|
107
86
|
raise FileNotFoundError(f"aidlc-state file not found: {state_file}")
|
|
108
87
|
# Anchor to the Current Stage line — the file accumulates history, so an
|
|
109
88
|
# unanchored search could silently pick up an earlier "Unit N" mention.
|
|
110
|
-
match = re.search(r"Current Stage[^\n]*Unit (\
|
|
89
|
+
match = re.search(r"Current Stage[^\n]*Unit ([\w-]+(?:\.[\w-]+)*)", state_file.read_text())
|
|
111
90
|
if not match:
|
|
112
91
|
raise RuntimeError(
|
|
113
92
|
f"Could not determine current unit from {state_file} "
|
|
114
|
-
"(no 'Unit
|
|
115
|
-
return
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def _normalize_unit(unit: str) -> str:
|
|
119
|
-
unit = unit.strip()
|
|
120
|
-
if unit[:1] in ("U", "u"):
|
|
121
|
-
unit = unit[1:]
|
|
122
|
-
return f"U{unit}"
|
|
93
|
+
"(no 'Unit <slug>' in the Current Stage line)")
|
|
94
|
+
return match.group(1)
|
|
123
95
|
|
|
124
96
|
|
|
125
97
|
class AidlcDocumentLoader:
|
|
126
|
-
"""`--
|
|
127
|
-
autodetect from aidlc-state.md."""
|
|
98
|
+
"""`--aidlc-unit` (a unit directory name, matched verbatim), or bare-run
|
|
99
|
+
autodetect from the Current Stage line of aidlc-state.md."""
|
|
128
100
|
|
|
129
101
|
name = "ai-dlc"
|
|
130
102
|
defaultable = True
|
|
@@ -133,15 +105,15 @@ class AidlcDocumentLoader:
|
|
|
133
105
|
self.docs_dir = Path(docs_dir)
|
|
134
106
|
|
|
135
107
|
def add_cli_args(self, parser: argparse.ArgumentParser) -> None:
|
|
136
|
-
parser.add_argument("--
|
|
137
|
-
help="AI-DLC unit
|
|
138
|
-
"(
|
|
139
|
-
"
|
|
140
|
-
"current unit in
|
|
108
|
+
parser.add_argument("--aidlc-unit", metavar="SLUG", default=None,
|
|
109
|
+
help="AI-DLC unit: the unit directory name under "
|
|
110
|
+
"aidlc-docs/construction/ (e.g. unit-3 or "
|
|
111
|
+
"support-for-windows), matched verbatim; "
|
|
112
|
+
"defaults to the current unit in "
|
|
113
|
+
"aidlc-docs/aidlc-state.md")
|
|
141
114
|
|
|
142
115
|
def resolve(self, args: argparse.Namespace) -> tuple[dict[str, str], str]:
|
|
143
|
-
unit =
|
|
144
|
-
else get_current_unit(self.docs_dir))
|
|
116
|
+
unit = args.aidlc_unit or get_current_unit(self.docs_dir)
|
|
145
117
|
return resolve_unit_docs(self.docs_dir, unit), f"unit {unit}"
|
|
146
118
|
|
|
147
119
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentic-dev-loop
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Opinionated LangGraph engine for OpenHands agent pipelines
|
|
5
5
|
Author: Luke Wilson
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -65,7 +65,7 @@ Requires Python ≥ 3.12.
|
|
|
65
65
|
```bash
|
|
66
66
|
export OPENAI_API_KEY=... # whatever env var your pipeline's api_key_env names
|
|
67
67
|
agentic-dev-loop agentic-dev-loop/pipeline.yaml --plan plan.md
|
|
68
|
-
agentic-dev-loop agentic-dev-loop/pipeline.yaml --
|
|
68
|
+
agentic-dev-loop agentic-dev-loop/pipeline.yaml --aidlc-unit unit-2
|
|
69
69
|
agentic-dev-loop agentic-dev-loop/pipeline.yaml --resume <thread-id> --max-attempts 5
|
|
70
70
|
./trigger-agentic-dev-loop.sh --plan plan.md # same thing, from the repo root
|
|
71
71
|
```
|
|
@@ -84,10 +84,12 @@ expanded and live-tails its console. To eyeball the viz page without
|
|
|
84
84
|
running a pipeline, `.venv/bin/python viz_demo.py` serves the real topology
|
|
85
85
|
and replays a scripted run (every node state, verdict badges, retry cycle).
|
|
86
86
|
|
|
87
|
-
`--
|
|
88
|
-
`
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
`--aidlc-unit` takes the unit directory name under
|
|
88
|
+
`aidlc-docs/construction/` verbatim — `unit-2`,
|
|
89
|
+
`unit-002-custom-configuration`, or any slug AI-DLC's own naming uses
|
|
90
|
+
(AI-DLC imposes no naming convention on unit directories). No spelling
|
|
91
|
+
tolerance, no numeric aliases: the slug must name the directory exactly,
|
|
92
|
+
and a wrong slug fails loudly listing the directories that do exist.
|
|
91
93
|
|
|
92
94
|
## What a run looks like
|
|
93
95
|
|
|
@@ -258,10 +260,10 @@ Loader-selection flow: a loader's flag in argv selects it; flags from two
|
|
|
258
260
|
loaders in one invocation error out; with no flag, the sole `defaultable`
|
|
259
261
|
loader autoruns (the bundled `ai-dlc` loader autodetects the current unit
|
|
260
262
|
from `aidlc-docs/aidlc-state.md`), otherwise the run proceeds with no
|
|
261
|
-
injected docs and the pipeline `name:` as subject.
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
263
|
+
injected docs and the pipeline `name:` as subject. A unit id must name
|
|
264
|
+
its directory exactly (`--aidlc-unit unit-3`); there is no numeric
|
|
265
|
+
fallback or spelling tolerance, and an unmatched slug fails loud listing
|
|
266
|
+
the available directories. A selected loader that
|
|
265
267
|
yields zero docs **fails fast** — context is never silently degraded.
|
|
266
268
|
Duplicate CLI option strings across loaders are a load-time error naming both.
|
|
267
269
|
|
|
@@ -303,7 +305,7 @@ The development lifecycle workflow lives in
|
|
|
303
305
|
phase details under `aws-aidlc-rule-details/`). Working docs — plans, user
|
|
304
306
|
stories, units of work, state — live in `aidlc-docs/`, created as the
|
|
305
307
|
workflow runs. The bundled `ai-dlc` document loader (see Extension points)
|
|
306
|
-
wires the pipeline's `--
|
|
308
|
+
wires the pipeline's `--aidlc-unit` flag to those docs.
|
|
307
309
|
|
|
308
310
|
OpenCode commands over the workflow: `/epic-status`, `/ticket-details`,
|
|
309
311
|
`/unit-status`, `/update-subtask` (ticket boards over `aidlc-docs/`),
|
|
@@ -14,10 +14,10 @@ agentic_dev_loop/results.py,sha256=wVB8k9dDgiLbfGxR7N2gaeX4eAMz6bIj19JRjXfe84E,1
|
|
|
14
14
|
agentic_dev_loop/stages.py,sha256=Ku9aku90T1S6MPn_wqgZNqLCswkGyQu5LQtwYTHmw1g,19109
|
|
15
15
|
agentic_dev_loop/viz.py,sha256=eMgfLBEssorTygdzNDqy1VLVxqw4uWjyxLJtcBBQWSI,42633
|
|
16
16
|
agentic_dev_loop/extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
agentic_dev_loop/extensions/aidlc.py,sha256=
|
|
18
|
-
agentic_dev_loop-0.
|
|
19
|
-
agentic_dev_loop-0.
|
|
20
|
-
agentic_dev_loop-0.
|
|
21
|
-
agentic_dev_loop-0.
|
|
22
|
-
agentic_dev_loop-0.
|
|
23
|
-
agentic_dev_loop-0.
|
|
17
|
+
agentic_dev_loop/extensions/aidlc.py,sha256=S9sUISIwjGJ9HOC3JFnrnPVHcxvW6E2kYkjdkILePH8,5277
|
|
18
|
+
agentic_dev_loop-0.3.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
19
|
+
agentic_dev_loop-0.3.0.dist-info/METADATA,sha256=jUdGT4sIvnUWyhdRQdH-egLUASQX0TauvwsqZYPz3wY,14937
|
|
20
|
+
agentic_dev_loop-0.3.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
21
|
+
agentic_dev_loop-0.3.0.dist-info/entry_points.txt,sha256=9i9TjC5AHjEfv7nM6RFdf06Okr9cj9Cgdfrd7OWSg5g,63
|
|
22
|
+
agentic_dev_loop-0.3.0.dist-info/top_level.txt,sha256=n1ktP1gXxGN6DJAnACR0mt276GRJB9PVDezf9DVPwaw,17
|
|
23
|
+
agentic_dev_loop-0.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|