agentic-dev-loop 1.0.0a48__tar.gz → 1.0.0a49__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.0a48/src/agentic_dev_loop.egg-info → agentic_dev_loop-1.0.0a49}/PKG-INFO +11 -10
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/README.md +10 -9
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/pyproject.toml +1 -1
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/core/config.py +62 -31
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/agents.py +6 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/dryrun.py +10 -3
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/graph.py +3 -4
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/stages.py +9 -8
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49/src/agentic_dev_loop.egg-info}/PKG-INFO +11 -10
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_033_brief_step.py +29 -62
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_041_brief_coverage.py +10 -10
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_agent_error_guide.py +1 -1
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_config.py +83 -7
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_e2e_fixtures.py +7 -8
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_publish.py +35 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/LICENSE +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/setup.cfg +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/core/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/core/paths.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/core/results.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/core/verdict.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/actions.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/agent_error_guide.md +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/agent_error_guide.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/allowlist.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/brief_coverage.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/brief_prompt.md +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/brief_prompt.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/cli.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/containers.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/deps.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/egress_policy.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/egress_proxy.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/forbidden.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/isolation.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/llm.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/loaders.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/mounts.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/outputs.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/presets.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/proc.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/registry.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/seccomp/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/seccomp/chromium-userns.json +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/secrets.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/step_workspace.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/sync_back.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/py.typed +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/viz/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/viz/__main__.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/viz/server.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop.egg-info/SOURCES.txt +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_028_declared_caches.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_029_merge_on_conflict.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_035_finalize_nudge.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_036_banned_commands_and_lane_boundary.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_046_brief_prompt.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_actions.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_agents.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_cli.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_containers.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_deps.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_dry_run.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_egress_proxy.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_graph.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_hostboundary_integration.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_isolation.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_isolation_integration.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_isolation_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_llm.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_loaders.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_module_contract.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_outputs.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_outputs_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_proc.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_qa_prompt.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_registry.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_repo_ascii.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_results.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_sdk_compat.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_secrets.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_secrets_integration.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_secrets_pipeline.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_source_resolution.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_stages.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_step_workspace.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_sync_back.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_trigger_script.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_unit5_config.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_unit5_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_unit5_pure.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_verdict.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_viz.py +0 -0
{agentic_dev_loop-1.0.0a48/src/agentic_dev_loop.egg-info → agentic_dev_loop-1.0.0a49}/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.0a49
|
|
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
|
|
@@ -206,10 +206,11 @@ agents_dir: sdk_agents # your <name>.agent.md f
|
|
|
206
206
|
|
|
207
207
|
steps:
|
|
208
208
|
- name: brief
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
209
|
+
reasoning_effort: medium
|
|
210
|
+
brief_inputs:
|
|
211
|
+
include: [AGENTS.md, coding-standards-ddd.yaml]
|
|
212
|
+
sections: [Repo rules digest, Unit scope digest, Environment facts, Open risks]
|
|
213
|
+
env_facts: ["the workspace is an export with no .git; verify with find/ls, never git status"]
|
|
213
214
|
on_pass: dev
|
|
214
215
|
|
|
215
216
|
- name: dev
|
|
@@ -249,9 +250,9 @@ steps:
|
|
|
249
250
|
|
|
250
251
|
| Key | Notes |
|
|
251
252
|
| --- | --- |
|
|
252
|
-
| `agent:` / `command:` / `action:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call). `commit` is the sole host-side built-in action; consumer actions are sandbox-executed, command-shaped scripts (see [Host boundary](#host-boundary-security-model)). |
|
|
253
|
+
| `agent:` / `command:` / `action:` / `brief_inputs:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call). `commit` is the sole host-side built-in action; consumer actions are sandbox-executed, command-shaped scripts (see [Host boundary](#host-boundary-security-model)). `brief_inputs:` is a native brief step - see its row below. |
|
|
253
254
|
| `produces:` / `artifact:` + `verdicts:` | Hygiene-tracked outputs vs verdict-parsed routing. The **last** `VERDICT:` line wins; stale artifacts are deleted at each attempt start. |
|
|
254
|
-
| `brief_inputs:` |
|
|
255
|
+
| `brief_inputs:` | The discriminator for a native brief step: `{ include: [<paths/globs>], env_facts: [<strings>], sections: [<headings>] }`. There is no `agent:`, `produces:`, or `max_iterations:` - the prompt is engine-embedded, the capture file is `attempt-brief.md`, and the iteration cap is 12, all engine-owned. The engine resolves the include list against the workspace, passes the resolved docs + include contents + env facts as the step's task message, and regenerates the step only when those inputs change. `sections` is the ordered list of digest headings the brief must emit (default `[Repo rules digest, Unit scope digest, Environment facts, Open risks]`; non-empty and unique). `name` stays free-form, so multiple brief steps are allowed. |
|
|
255
256
|
| `terminal_timeout:` | Optional agent-step per-action timeout in seconds (gt 0). `SandboxTerminalExecutor` uses it when the agent leaves the terminal action timeout unset, overriding the SDK tool's default. `None` = current behavior. |
|
|
256
257
|
| `finalize_nudge_turns:` | Optional agent-step final-response nudge (gt 0). Requires `max_iterations:` and must be less than it. At `max_iterations - finalize_nudge_turns` completed turns the engine injects one advisory turn ("`N turns remain: produce your final response now`"), exactly once; the agent may still exhaust its turns and existing stuck/unclear handling applies unchanged. Absent = current behavior. Provisional pending one consumer run (revert if noisy or the raised cap inflates cost). |
|
|
257
258
|
| `custom_prompt:` | Planned (planning-docs/040). Optional agent-step value: literal text, or a path whose file contents are read at load time, appended verbatim to the agent prompt at context assembly (after the prompt, before the task/artifacts). A value that looks like a path but names no file is a validation error. Agent steps only. |
|
|
@@ -294,9 +295,9 @@ Recommended lane defaults (guidance only, not enforced):
|
|
|
294
295
|
| review | xhigh | shortest lane, highest stakes: a missed detection sails past the last gate into commit |
|
|
295
296
|
|
|
296
297
|
A `brief_inputs:` step is the per-run context-digestion pattern: it runs once at
|
|
297
|
-
run start (or when its inputs change), its `
|
|
298
|
-
starts, and the dev/review agents read that digest
|
|
299
|
-
rules and environment facts every attempt.
|
|
298
|
+
run start (or when its inputs change), its engine-owned `attempt-brief.md`
|
|
299
|
+
digest survives attempt starts, and the dev/review agents read that digest
|
|
300
|
+
instead of re-deriving repo rules and environment facts every attempt.
|
|
300
301
|
|
|
301
302
|
The brief digest is mechanically coverage-checked: the configured
|
|
302
303
|
`brief_inputs.sections` headings must each appear on their own line, every
|
|
@@ -162,10 +162,11 @@ agents_dir: sdk_agents # your <name>.agent.md f
|
|
|
162
162
|
|
|
163
163
|
steps:
|
|
164
164
|
- name: brief
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
165
|
+
reasoning_effort: medium
|
|
166
|
+
brief_inputs:
|
|
167
|
+
include: [AGENTS.md, coding-standards-ddd.yaml]
|
|
168
|
+
sections: [Repo rules digest, Unit scope digest, Environment facts, Open risks]
|
|
169
|
+
env_facts: ["the workspace is an export with no .git; verify with find/ls, never git status"]
|
|
169
170
|
on_pass: dev
|
|
170
171
|
|
|
171
172
|
- name: dev
|
|
@@ -205,9 +206,9 @@ steps:
|
|
|
205
206
|
|
|
206
207
|
| Key | Notes |
|
|
207
208
|
| --- | --- |
|
|
208
|
-
| `agent:` / `command:` / `action:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call). `commit` is the sole host-side built-in action; consumer actions are sandbox-executed, command-shaped scripts (see [Host boundary](#host-boundary-security-model)). |
|
|
209
|
+
| `agent:` / `command:` / `action:` / `brief_inputs:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call). `commit` is the sole host-side built-in action; consumer actions are sandbox-executed, command-shaped scripts (see [Host boundary](#host-boundary-security-model)). `brief_inputs:` is a native brief step - see its row below. |
|
|
209
210
|
| `produces:` / `artifact:` + `verdicts:` | Hygiene-tracked outputs vs verdict-parsed routing. The **last** `VERDICT:` line wins; stale artifacts are deleted at each attempt start. |
|
|
210
|
-
| `brief_inputs:` |
|
|
211
|
+
| `brief_inputs:` | The discriminator for a native brief step: `{ include: [<paths/globs>], env_facts: [<strings>], sections: [<headings>] }`. There is no `agent:`, `produces:`, or `max_iterations:` - the prompt is engine-embedded, the capture file is `attempt-brief.md`, and the iteration cap is 12, all engine-owned. The engine resolves the include list against the workspace, passes the resolved docs + include contents + env facts as the step's task message, and regenerates the step only when those inputs change. `sections` is the ordered list of digest headings the brief must emit (default `[Repo rules digest, Unit scope digest, Environment facts, Open risks]`; non-empty and unique). `name` stays free-form, so multiple brief steps are allowed. |
|
|
211
212
|
| `terminal_timeout:` | Optional agent-step per-action timeout in seconds (gt 0). `SandboxTerminalExecutor` uses it when the agent leaves the terminal action timeout unset, overriding the SDK tool's default. `None` = current behavior. |
|
|
212
213
|
| `finalize_nudge_turns:` | Optional agent-step final-response nudge (gt 0). Requires `max_iterations:` and must be less than it. At `max_iterations - finalize_nudge_turns` completed turns the engine injects one advisory turn ("`N turns remain: produce your final response now`"), exactly once; the agent may still exhaust its turns and existing stuck/unclear handling applies unchanged. Absent = current behavior. Provisional pending one consumer run (revert if noisy or the raised cap inflates cost). |
|
|
213
214
|
| `custom_prompt:` | Planned (planning-docs/040). Optional agent-step value: literal text, or a path whose file contents are read at load time, appended verbatim to the agent prompt at context assembly (after the prompt, before the task/artifacts). A value that looks like a path but names no file is a validation error. Agent steps only. |
|
|
@@ -250,9 +251,9 @@ Recommended lane defaults (guidance only, not enforced):
|
|
|
250
251
|
| review | xhigh | shortest lane, highest stakes: a missed detection sails past the last gate into commit |
|
|
251
252
|
|
|
252
253
|
A `brief_inputs:` step is the per-run context-digestion pattern: it runs once at
|
|
253
|
-
run start (or when its inputs change), its `
|
|
254
|
-
starts, and the dev/review agents read that digest
|
|
255
|
-
rules and environment facts every attempt.
|
|
254
|
+
run start (or when its inputs change), its engine-owned `attempt-brief.md`
|
|
255
|
+
digest survives attempt starts, and the dev/review agents read that digest
|
|
256
|
+
instead of re-deriving repo rules and environment facts every attempt.
|
|
256
257
|
|
|
257
258
|
The brief digest is mechanically coverage-checked: the configured
|
|
258
259
|
`brief_inputs.sections` headings must each appear on their own line, every
|
|
@@ -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.0a49"
|
|
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"
|
|
@@ -35,6 +35,13 @@ DEFAULT_BRIEF_SECTIONS = [
|
|
|
35
35
|
"Open risks",
|
|
36
36
|
]
|
|
37
37
|
|
|
38
|
+
# 049: a brief step is engine-owned end to end - the capture file and the
|
|
39
|
+
# iteration cap are fixed, not configurable. Downstream consumers (capture
|
|
40
|
+
# write, hygiene, context, retry classification) keep reading `produces` and
|
|
41
|
+
# `max_iterations`; parse-time normalization applies these after validation.
|
|
42
|
+
BRIEF_PRODUCES = ["attempt-brief.md"]
|
|
43
|
+
BRIEF_MAX_ITERATIONS = 12
|
|
44
|
+
|
|
38
45
|
# 047: the full SDK reasoning_effort literal, passed through verbatim. The
|
|
39
46
|
# engine is provider-generic, so it validates shape only - whether a given
|
|
40
47
|
# provider accepts every value (notably "none") is operator risk (D3).
|
|
@@ -372,9 +379,9 @@ class BriefInputsSpec(BaseModel):
|
|
|
372
379
|
|
|
373
380
|
class StepConfig(BaseModel):
|
|
374
381
|
"""One pipeline step. Its type IS the key it carries: exactly one of
|
|
375
|
-
`agent`, `action`, or `
|
|
376
|
-
else fails to load. See the `kind` property (the single
|
|
377
|
-
graph-building dispatches on)."""
|
|
382
|
+
`agent`, `action`, `command`, or `brief_inputs` - empty values do not
|
|
383
|
+
count, anything else fails to load. See the `kind` property (the single
|
|
384
|
+
discriminator graph-building dispatches on)."""
|
|
378
385
|
model_config = ConfigDict(extra="forbid")
|
|
379
386
|
|
|
380
387
|
name: str
|
|
@@ -413,10 +420,12 @@ class StepConfig(BaseModel):
|
|
|
413
420
|
|
|
414
421
|
@property
|
|
415
422
|
def kind(self) -> str:
|
|
416
|
-
"""The step's type: 'agent', 'command', or 'action'. Which
|
|
417
|
-
|
|
423
|
+
"""The step's type: 'brief', 'agent', 'command', or 'action'. Which
|
|
424
|
+
of the four keys is set is the ONLY signal; validation has already
|
|
418
425
|
guaranteed exactly one is set and non-empty by the time any instance
|
|
419
426
|
exists."""
|
|
427
|
+
if self.brief_inputs is not None:
|
|
428
|
+
return "brief"
|
|
420
429
|
if self.agent is not None:
|
|
421
430
|
return "agent"
|
|
422
431
|
if self.command is not None:
|
|
@@ -470,11 +479,48 @@ class StepConfig(BaseModel):
|
|
|
470
479
|
raise ValueError(
|
|
471
480
|
f"step {self.name!r}: {key!r} must be a non-empty string"
|
|
472
481
|
)
|
|
473
|
-
if sum(k is not None for k in
|
|
482
|
+
if sum(k is not None for k in
|
|
483
|
+
(self.agent, self.action, self.command, self.brief_inputs)) != 1:
|
|
474
484
|
raise ValueError(
|
|
475
|
-
f"step {self.name!r}: exactly one of 'agent', 'action',
|
|
476
|
-
"'command' is required"
|
|
485
|
+
f"step {self.name!r}: exactly one of 'agent', 'action', "
|
|
486
|
+
"'command', or 'brief_inputs' is required"
|
|
477
487
|
)
|
|
488
|
+
if self.brief_inputs is not None:
|
|
489
|
+
if self.produces:
|
|
490
|
+
raise ValueError(
|
|
491
|
+
f"step {self.name!r}: 'brief_inputs' steps write "
|
|
492
|
+
f"{BRIEF_PRODUCES[0]!r}; drop 'produces'")
|
|
493
|
+
if self.artifact is not None:
|
|
494
|
+
raise ValueError(
|
|
495
|
+
f"step {self.name!r}: 'brief_inputs' steps have no "
|
|
496
|
+
"verdicts; drop 'artifact'")
|
|
497
|
+
if self.verdicts is not None:
|
|
498
|
+
raise ValueError(
|
|
499
|
+
f"step {self.name!r}: 'brief_inputs' steps have no "
|
|
500
|
+
"verdicts; drop 'verdicts'")
|
|
501
|
+
if self.params:
|
|
502
|
+
raise ValueError(
|
|
503
|
+
f"step {self.name!r}: 'brief_inputs' steps take no "
|
|
504
|
+
"'params'")
|
|
505
|
+
if self.max_iterations is not None:
|
|
506
|
+
raise ValueError(
|
|
507
|
+
f"step {self.name!r}: 'brief_inputs' steps use the "
|
|
508
|
+
f"engine cap ({BRIEF_MAX_ITERATIONS}); drop "
|
|
509
|
+
"'max_iterations'")
|
|
510
|
+
if self.timeout is not None:
|
|
511
|
+
raise ValueError(
|
|
512
|
+
f"step {self.name!r}: 'timeout' is command-only; drop it "
|
|
513
|
+
"from a 'brief_inputs' step")
|
|
514
|
+
if self.custom_prompt is not None:
|
|
515
|
+
raise ValueError(
|
|
516
|
+
f"step {self.name!r}: the brief prompt is engine-owned; "
|
|
517
|
+
"drop 'custom_prompt'")
|
|
518
|
+
if self.finalize_nudge_turns is not None:
|
|
519
|
+
raise ValueError(
|
|
520
|
+
f"step {self.name!r}: 'brief_inputs' steps have no "
|
|
521
|
+
"verdicts; drop 'finalize_nudge_turns'")
|
|
522
|
+
self.produces = list(BRIEF_PRODUCES)
|
|
523
|
+
self.max_iterations = BRIEF_MAX_ITERATIONS
|
|
478
524
|
if self.command is not None:
|
|
479
525
|
for field, empty in (("artifact", None), ("verdicts", None),
|
|
480
526
|
("produces", []), ("params", {}),
|
|
@@ -495,9 +541,11 @@ class StepConfig(BaseModel):
|
|
|
495
541
|
raise ValueError(
|
|
496
542
|
f"step {self.name!r}: 'timeout' requires 'command'"
|
|
497
543
|
)
|
|
498
|
-
if self.terminal_timeout is not None
|
|
544
|
+
if (self.terminal_timeout is not None
|
|
545
|
+
and self.agent is None and self.brief_inputs is None):
|
|
499
546
|
raise ValueError(
|
|
500
|
-
f"step {self.name!r}: 'terminal_timeout' requires 'agent'"
|
|
547
|
+
f"step {self.name!r}: 'terminal_timeout' requires 'agent' or "
|
|
548
|
+
"'brief_inputs'"
|
|
501
549
|
)
|
|
502
550
|
if self.finalize_nudge_turns is not None and self.agent is None:
|
|
503
551
|
raise ValueError(
|
|
@@ -514,37 +562,20 @@ class StepConfig(BaseModel):
|
|
|
514
562
|
f"step {self.name!r}: 'finalize_nudge_turns' must be less "
|
|
515
563
|
"than 'max_iterations'"
|
|
516
564
|
)
|
|
517
|
-
if self.brief_inputs is not None and self.agent is None:
|
|
518
|
-
raise ValueError(
|
|
519
|
-
f"step {self.name!r}: 'brief_inputs' requires 'agent'"
|
|
520
|
-
)
|
|
521
565
|
if self.custom_prompt is not None and self.agent is None:
|
|
522
566
|
raise ValueError(
|
|
523
567
|
f"step {self.name!r}: 'custom_prompt' requires 'agent'"
|
|
524
568
|
)
|
|
525
|
-
if (self.
|
|
526
|
-
and
|
|
527
|
-
raise ValueError(
|
|
528
|
-
f"step {self.name!r}: 'brief_inputs' is only valid on a "
|
|
529
|
-
"produces-only agent step"
|
|
530
|
-
)
|
|
531
|
-
if self.brief_inputs is not None and not self.produces:
|
|
532
|
-
raise ValueError(
|
|
533
|
-
f"step {self.name!r}: 'brief_inputs' requires 'produces'"
|
|
534
|
-
)
|
|
535
|
-
if self.setup is not None and self.command is None and self.agent is None:
|
|
569
|
+
if (self.setup is not None and self.command is None
|
|
570
|
+
and self.agent is None and self.brief_inputs is None):
|
|
536
571
|
raise ValueError(
|
|
537
|
-
f"step {self.name!r}: 'setup' is only allowed on 'command'
|
|
572
|
+
f"step {self.name!r}: 'setup' is only allowed on 'command', "
|
|
573
|
+
"'agent', or 'brief_inputs' steps"
|
|
538
574
|
)
|
|
539
575
|
if self.setup_probe is not None and self.setup is None:
|
|
540
576
|
raise ValueError(
|
|
541
577
|
f"step {self.name!r}: 'setup_probe' requires 'setup'"
|
|
542
578
|
)
|
|
543
|
-
# `network` is NOT rejected here: the schema can't tell a host-side
|
|
544
|
-
# built-in action (e.g. `commit`, which must never take egress) from
|
|
545
|
-
# a sandbox-executed consumer action (which may, like a command step).
|
|
546
|
-
# That distinction is enforced once the registry is known - see
|
|
547
|
-
# `validate_action_network` (020).
|
|
548
579
|
if self.secrets and self.action is not None:
|
|
549
580
|
raise ValueError(
|
|
550
581
|
f"step {self.name!r}: 'secrets' is only allowed on 'agent' or "
|
{agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/agents.py
RENAMED
|
@@ -25,6 +25,12 @@ if TYPE_CHECKING:
|
|
|
25
25
|
|
|
26
26
|
from .isolation import Sandbox
|
|
27
27
|
|
|
28
|
+
# 049, D3: the internal brief agent name is a fixed UUID4 string, never the
|
|
29
|
+
# literal "brief", so a consumer vendoring a `brief.agent.md` cannot mint a
|
|
30
|
+
# second AgentDefinition under the same name. The vendored-file warning still
|
|
31
|
+
# keys on the literal filename `brief.agent.md` (that is the file to delete).
|
|
32
|
+
BRIEF_AGENT_NAME = "4f1c9e2b-7a3d-4e5f-8b6c-0d1a2f3c4d5e"
|
|
33
|
+
|
|
28
34
|
|
|
29
35
|
def load_agent(agents_dir: Path | str, name: str, llm: LLM) -> "Agent":
|
|
30
36
|
"""Load `<agents_dir>/<name>.agent.md` and build the Agent via the SDK
|
{agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/dryrun.py
RENAMED
|
@@ -27,10 +27,11 @@ def _validate_agents(cfg: PipelineConfig, agents_dir: Path) -> None:
|
|
|
27
27
|
"""Every `agent:` step must resolve to an existing file under agents_dir
|
|
28
28
|
(the same fail-fast `load_agent` enforces at run time).
|
|
29
29
|
|
|
30
|
-
The brief prompt is engine-embedded (046), so a ``brief_inputs:``
|
|
31
|
-
|
|
30
|
+
The brief prompt is engine-embedded (046), so a native ``brief_inputs:``
|
|
31
|
+
step (kind 'brief') has `agent is None` and is exempt from the agents_dir
|
|
32
|
+
lookup - it never reads a vendored file."""
|
|
32
33
|
for step in cfg.steps:
|
|
33
|
-
if step.agent is None
|
|
34
|
+
if step.agent is None:
|
|
34
35
|
continue
|
|
35
36
|
path = agents_dir / f"{step.agent}.agent.md"
|
|
36
37
|
if not path.is_file():
|
|
@@ -79,6 +80,12 @@ def _step_summary(step: StepConfig) -> str:
|
|
|
79
80
|
parts.append(f"produces={','.join(step.produces)}")
|
|
80
81
|
if step.max_iterations is not None:
|
|
81
82
|
parts.append(f"max_iterations={step.max_iterations}")
|
|
83
|
+
elif step.kind == "brief":
|
|
84
|
+
parts = [f"sections={len(step.brief_inputs.sections)}"]
|
|
85
|
+
if step.produces:
|
|
86
|
+
parts.append(f"produces={','.join(step.produces)}")
|
|
87
|
+
if step.max_iterations is not None:
|
|
88
|
+
parts.append(f"max_iterations={step.max_iterations}")
|
|
82
89
|
elif step.kind == "command":
|
|
83
90
|
parts = [f"command={step.command}"]
|
|
84
91
|
if step.timeout is not None:
|
{agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/graph.py
RENAMED
|
@@ -93,10 +93,9 @@ def build_graph(runtime: Runtime, checkpointer=None):
|
|
|
93
93
|
graph = StateGraph(LoopState)
|
|
94
94
|
for step in runtime.cfg.steps:
|
|
95
95
|
if step.kind == "agent":
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
node = make_agent_node(runtime, step)
|
|
96
|
+
node = make_agent_node(runtime, step)
|
|
97
|
+
elif step.kind == "brief":
|
|
98
|
+
node = make_brief_node(runtime, step)
|
|
100
99
|
elif step.kind == "command":
|
|
101
100
|
node = make_command_node(runtime, step)
|
|
102
101
|
else:
|
{agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/stages.py
RENAMED
|
@@ -32,7 +32,8 @@ from langgraph.graph import END
|
|
|
32
32
|
from openhands.sdk import Conversation
|
|
33
33
|
|
|
34
34
|
from .agent_error_guide import guide_section
|
|
35
|
-
from .agents import load_agent, load_brief_agent,
|
|
35
|
+
from .agents import (BRIEF_AGENT_NAME, load_agent, load_brief_agent,
|
|
36
|
+
register_sandbox_terminal)
|
|
36
37
|
from .brief_coverage import extract_anchors, missing_anchors
|
|
37
38
|
from .brief_prompt import BRIEF_PROMPT_VERSION
|
|
38
39
|
from agentic_dev_loop.core.config import TERMINALS, PipelineConfig, Route, StepConfig, route_target
|
|
@@ -182,7 +183,7 @@ class Runtime:
|
|
|
182
183
|
if step is not None and step.reasoning_effort is not None:
|
|
183
184
|
llm = build_llm(self.cfg.llm,
|
|
184
185
|
reasoning_effort=step.reasoning_effort)
|
|
185
|
-
if step is not None and step.
|
|
186
|
+
if step is not None and step.kind == "brief":
|
|
186
187
|
agent = load_brief_agent(
|
|
187
188
|
agent_name, step.brief_inputs.sections, llm)
|
|
188
189
|
else:
|
|
@@ -398,7 +399,7 @@ def build_context(runtime: Runtime, docs: dict[str, str], subject: str,
|
|
|
398
399
|
parts.append("## Dependency policy\n\n" + policy_injection(runtime.cfg))
|
|
399
400
|
lines = []
|
|
400
401
|
for step in runtime.cfg.steps:
|
|
401
|
-
if step.agent
|
|
402
|
+
if step.kind not in ("agent", "brief"):
|
|
402
403
|
continue
|
|
403
404
|
for produced in step.produces:
|
|
404
405
|
lines.append(f"{step.name} agent writes: `{prompt_dir(runtime)}/{produced}`")
|
|
@@ -428,7 +429,7 @@ def run_scoped_artifacts(cfg: PipelineConfig) -> list[str]:
|
|
|
428
429
|
outputs are exempt from per-attempt hygiene."""
|
|
429
430
|
files = []
|
|
430
431
|
for step in cfg.steps:
|
|
431
|
-
if step.
|
|
432
|
+
if step.kind != "brief":
|
|
432
433
|
continue
|
|
433
434
|
files.extend(step.produces)
|
|
434
435
|
if step.artifact is not None:
|
|
@@ -778,7 +779,7 @@ def _warn_vendored_brief(runtime: Runtime, step: StepConfig) -> None:
|
|
|
778
779
|
The embedded prompt is authoritative, so a leftover
|
|
779
780
|
``<agents_dir>/<agent>.agent.md`` for a ``brief_inputs:`` step is ignored;
|
|
780
781
|
the warning names it without failing the run."""
|
|
781
|
-
vendored = runtime.agents_dir /
|
|
782
|
+
vendored = runtime.agents_dir / "brief.agent.md"
|
|
782
783
|
if vendored.is_file():
|
|
783
784
|
print(
|
|
784
785
|
f"[{runtime.cfg.name}] ignoring vendored brief prompt {vendored} - "
|
|
@@ -845,8 +846,8 @@ def make_brief_node(runtime: Runtime, step: StepConfig):
|
|
|
845
846
|
state.get("doc_sources"),
|
|
846
847
|
coverage_note=coverage_note)
|
|
847
848
|
|
|
848
|
-
print(f"[{subject}] Running {step.
|
|
849
|
-
result = runtime.run_agent(
|
|
849
|
+
print(f"[{subject}] Running {step.name} agent...", flush=True)
|
|
850
|
+
result = runtime.run_agent(BRIEF_AGENT_NAME, message,
|
|
850
851
|
max_iterations=max_iterations, step=step)
|
|
851
852
|
if not result.ok:
|
|
852
853
|
print(f"[{subject}] {step.name} agent failed on attempt {attempt}.",
|
|
@@ -1125,7 +1126,7 @@ def make_bump_node(runtime: Runtime):
|
|
|
1125
1126
|
# re-enter that brief step so it regenerates once and refreshes its
|
|
1126
1127
|
# hash. Unchanged inputs keep the failing step's retry_target.
|
|
1127
1128
|
for step in runtime.cfg.steps:
|
|
1128
|
-
if step.
|
|
1129
|
+
if step.kind != "brief":
|
|
1129
1130
|
continue
|
|
1130
1131
|
try:
|
|
1131
1132
|
current = brief_inputs_hash(runtime, step,
|
{agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49/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.0a49
|
|
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
|
|
@@ -206,10 +206,11 @@ agents_dir: sdk_agents # your <name>.agent.md f
|
|
|
206
206
|
|
|
207
207
|
steps:
|
|
208
208
|
- name: brief
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
209
|
+
reasoning_effort: medium
|
|
210
|
+
brief_inputs:
|
|
211
|
+
include: [AGENTS.md, coding-standards-ddd.yaml]
|
|
212
|
+
sections: [Repo rules digest, Unit scope digest, Environment facts, Open risks]
|
|
213
|
+
env_facts: ["the workspace is an export with no .git; verify with find/ls, never git status"]
|
|
213
214
|
on_pass: dev
|
|
214
215
|
|
|
215
216
|
- name: dev
|
|
@@ -249,9 +250,9 @@ steps:
|
|
|
249
250
|
|
|
250
251
|
| Key | Notes |
|
|
251
252
|
| --- | --- |
|
|
252
|
-
| `agent:` / `command:` / `action:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call). `commit` is the sole host-side built-in action; consumer actions are sandbox-executed, command-shaped scripts (see [Host boundary](#host-boundary-security-model)). |
|
|
253
|
+
| `agent:` / `command:` / `action:` / `brief_inputs:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call). `commit` is the sole host-side built-in action; consumer actions are sandbox-executed, command-shaped scripts (see [Host boundary](#host-boundary-security-model)). `brief_inputs:` is a native brief step - see its row below. |
|
|
253
254
|
| `produces:` / `artifact:` + `verdicts:` | Hygiene-tracked outputs vs verdict-parsed routing. The **last** `VERDICT:` line wins; stale artifacts are deleted at each attempt start. |
|
|
254
|
-
| `brief_inputs:` |
|
|
255
|
+
| `brief_inputs:` | The discriminator for a native brief step: `{ include: [<paths/globs>], env_facts: [<strings>], sections: [<headings>] }`. There is no `agent:`, `produces:`, or `max_iterations:` - the prompt is engine-embedded, the capture file is `attempt-brief.md`, and the iteration cap is 12, all engine-owned. The engine resolves the include list against the workspace, passes the resolved docs + include contents + env facts as the step's task message, and regenerates the step only when those inputs change. `sections` is the ordered list of digest headings the brief must emit (default `[Repo rules digest, Unit scope digest, Environment facts, Open risks]`; non-empty and unique). `name` stays free-form, so multiple brief steps are allowed. |
|
|
255
256
|
| `terminal_timeout:` | Optional agent-step per-action timeout in seconds (gt 0). `SandboxTerminalExecutor` uses it when the agent leaves the terminal action timeout unset, overriding the SDK tool's default. `None` = current behavior. |
|
|
256
257
|
| `finalize_nudge_turns:` | Optional agent-step final-response nudge (gt 0). Requires `max_iterations:` and must be less than it. At `max_iterations - finalize_nudge_turns` completed turns the engine injects one advisory turn ("`N turns remain: produce your final response now`"), exactly once; the agent may still exhaust its turns and existing stuck/unclear handling applies unchanged. Absent = current behavior. Provisional pending one consumer run (revert if noisy or the raised cap inflates cost). |
|
|
257
258
|
| `custom_prompt:` | Planned (planning-docs/040). Optional agent-step value: literal text, or a path whose file contents are read at load time, appended verbatim to the agent prompt at context assembly (after the prompt, before the task/artifacts). A value that looks like a path but names no file is a validation error. Agent steps only. |
|
|
@@ -294,9 +295,9 @@ Recommended lane defaults (guidance only, not enforced):
|
|
|
294
295
|
| review | xhigh | shortest lane, highest stakes: a missed detection sails past the last gate into commit |
|
|
295
296
|
|
|
296
297
|
A `brief_inputs:` step is the per-run context-digestion pattern: it runs once at
|
|
297
|
-
run start (or when its inputs change), its `
|
|
298
|
-
starts, and the dev/review agents read that digest
|
|
299
|
-
rules and environment facts every attempt.
|
|
298
|
+
run start (or when its inputs change), its engine-owned `attempt-brief.md`
|
|
299
|
+
digest survives attempt starts, and the dev/review agents read that digest
|
|
300
|
+
instead of re-deriving repo rules and environment facts every attempt.
|
|
300
301
|
|
|
301
302
|
The brief digest is mechanically coverage-checked: the configured
|
|
302
303
|
`brief_inputs.sections` headings must each appear on their own line, every
|