agentic-dev-loop 0.10.2__tar.gz → 0.10.3__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-0.10.2/src/agentic_dev_loop.egg-info → agentic_dev_loop-0.10.3}/PKG-INFO +19 -1
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/README.md +18 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/pyproject.toml +1 -1
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/forbidden.py +12 -26
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/onboarding/recipes/dotnet-node.yaml +37 -4
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/onboarding/recipes/python.yaml +25 -4
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/stages.py +1 -2
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3/src/agentic_dev_loop.egg-info}/PKG-INFO +19 -1
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_036_banned_commands_and_lane_boundary.py +7 -43
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_055_recipes.py +6 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/LICENSE +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/setup.cfg +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/__init__.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/core/__init__.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/core/config.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/core/paths.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/core/profiles/__init__.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/core/profiles/data/dotnet-node.yaml +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/core/profiles/data/python.yaml +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/core/results.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/core/verdict.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/__init__.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/actions.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/agent_error_guide.md +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/agent_error_guide.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/agents.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/allowlist.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/brief_coverage.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/brief_prompt.md +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/brief_prompt.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/cli.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/containers.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/deps.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/dryrun.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/egress_policy.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/egress_proxy.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/graph.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/hostboundary.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/isolation.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/llm.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/loaders.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/mounts.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/onboarding/__init__.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/onboarding/checks.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/onboarding/doctor.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/onboarding/init.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/onboarding/recipes.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/onboarding/templates/dev.agent.md +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/onboarding/templates/env.example +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/onboarding/templates/qa.agent.md +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/onboarding/templates/review.agent.md +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/onboarding/templates/trigger-agentic-dev-loop.sh +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/onboarding/templates.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/outputs.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/presets.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/proc.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/registry.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/seccomp/__init__.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/seccomp/chromium-userns.json +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/secrets.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/step_workspace.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/sync_back.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/py.typed +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/viz/__init__.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/viz/__main__.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/viz/server.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop.egg-info/SOURCES.txt +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_029_merge_on_conflict.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_033_brief_step.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_035_finalize_nudge.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_041_brief_coverage.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_046_brief_prompt.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_055_cli.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_055_doctor.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_055_init.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_055_profile_loading.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_055_profiles.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_055_schema_lock.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_055_templates.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_actions.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_agent_error_guide.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_agents.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_aidlc.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_cli.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_config.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_containers.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_deps.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_dry_run.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_e2e_fixtures.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_egress_proxy.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_graph.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_hostboundary.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_hostboundary_integration.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_isolation.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_isolation_integration.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_isolation_pbt.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_llm.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_loaders.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_module_contract.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_outputs.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_outputs_pbt.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_proc.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_publish.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_qa_prompt.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_registry.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_repo_ascii.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_results.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_sdk_compat.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_secrets.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_secrets_integration.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_secrets_pipeline.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_source_resolution.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_stages.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_step_workspace.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_sync_back.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_trigger_script.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_unit5_config.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_unit5_pbt.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_unit5_pure.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_verdict.py +0 -0
- {agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/tests/test_viz.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentic-dev-loop
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.3
|
|
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
|
|
@@ -229,6 +229,7 @@ steps:
|
|
|
229
229
|
agent: dev
|
|
230
230
|
produces: implementation-summary.md
|
|
231
231
|
terminal_timeout: 900
|
|
232
|
+
# forbidden_commands: ["npm install", "npm ci"] # per-step tool deny-list; engine defaults are git mutations + docker
|
|
232
233
|
on_pass: test
|
|
233
234
|
|
|
234
235
|
- name: test
|
|
@@ -268,6 +269,7 @@ steps:
|
|
|
268
269
|
| `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. |
|
|
269
270
|
| `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. |
|
|
270
271
|
| `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). |
|
|
272
|
+
| `forbidden_commands:` | Optional per-step list of regex patterns. The sandbox terminal matches them against each agent command before any shell spawn; a match returns a structured `[forbidden: <label>]` observation instead of running. A single string is coerced to a one-item list; an invalid regex fails at load. Defaults are run invariants only (git mutations, docker); tool-specific rules belong to the toolchain profile/recipe or the pipeline author. Command steps are unaffected. See [Banned commands](#banned-commands-forbidden_commands). |
|
|
271
273
|
| `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. |
|
|
272
274
|
| `llms:` (top-level) / step `llm:` | Named LLM profiles, each a full `LlmConfig` (`model`, `base_url`, `api_key_env`, `timeout`, `reasoning_effort`). A step names one with `llm: <profile-name>`; the whole profile moves together (no partial merges). Allowed only on agent and brief steps. The top-level `llm:` block remains the default. Profile keys are host-side - sandbox access still goes through `secrets:`. |
|
|
273
275
|
| `reasoning_effort:` | Optional `low` / `medium` / `high` / `xhigh` / `none`. Set on the `llm` block, a `llms:` profile, or per-step (a step value overrides the effective profile's). Unset anywhere = reasoning off: nothing is forwarded and reasoning is stripped from requests. A thinking model without an effort level is invalid config and fails at the first request (crap in, crap out). See [Reasoning effort](#reasoning-effort-opt-in). |
|
|
@@ -284,6 +286,22 @@ steps:
|
|
|
284
286
|
| `caches:` / `sync_excludes:` | Top-level run-wide lists. `caches:` names repo-relative toolchain cache dirs bind-mounted rw from host-side stores under `{state_dir}/caches/<name>` (auto-created); a `setup:` hook can warm them (the provision container carries the same cache mounts); `sync_excludes:` names non-gitignored scratch paths skipped from both the export copy and sync-back (wildcard `*` spans separators, so `**/bin/**` matches a `/bin/` segment at any depth). Both default to `[]`. See [docs/setup.md](docs/setup.md). |
|
|
285
287
|
| `merge_on_conflict:` | Top-level run-wide list of append-style logs (same path vocabulary as `sync_excludes:`, default `[]`). A concurrent host edit to a listed path is union-merged instead of raising: the host file wins and the sandbox's export-only lines are appended in order; both sides converge and the run continues. See [docs/setup.md](docs/setup.md). |
|
|
286
288
|
|
|
289
|
+
### Banned commands (`forbidden_commands:`)
|
|
290
|
+
|
|
291
|
+
`forbidden_commands:` is a per-step list of regex patterns. The sandbox
|
|
292
|
+
terminal checks them against every command the agent runs, before any shell
|
|
293
|
+
spawn: a match returns a structured `[forbidden: <label>]` observation instead
|
|
294
|
+
of executing. The engine defaults are run invariants only - git mutations and
|
|
295
|
+
docker - so the deny-list is network-independent by design: `network:` stays a
|
|
296
|
+
docker/egress concern. A single string is coerced to a one-item list; an
|
|
297
|
+
invalid regex is a load error. Command steps bypass the filter by design.
|
|
298
|
+
|
|
299
|
+
Tool-specific rules are not engine defaults. A toolchain profile or bundled
|
|
300
|
+
recipe declares them per step (for example the `python` recipe denies
|
|
301
|
+
`pip install`, `uv add`, and `pip-compile` on its agent steps), and a
|
|
302
|
+
consumer pipeline authors its own for its toolchain. There is no disable
|
|
303
|
+
escape hatch for the engine defaults.
|
|
304
|
+
|
|
287
305
|
### Reasoning effort (opt-in)
|
|
288
306
|
|
|
289
307
|
`reasoning_effort` is an opt-in latency dial on the pipeline `llm` block, a
|
|
@@ -185,6 +185,7 @@ steps:
|
|
|
185
185
|
agent: dev
|
|
186
186
|
produces: implementation-summary.md
|
|
187
187
|
terminal_timeout: 900
|
|
188
|
+
# forbidden_commands: ["npm install", "npm ci"] # per-step tool deny-list; engine defaults are git mutations + docker
|
|
188
189
|
on_pass: test
|
|
189
190
|
|
|
190
191
|
- name: test
|
|
@@ -224,6 +225,7 @@ steps:
|
|
|
224
225
|
| `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. |
|
|
225
226
|
| `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. |
|
|
226
227
|
| `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). |
|
|
228
|
+
| `forbidden_commands:` | Optional per-step list of regex patterns. The sandbox terminal matches them against each agent command before any shell spawn; a match returns a structured `[forbidden: <label>]` observation instead of running. A single string is coerced to a one-item list; an invalid regex fails at load. Defaults are run invariants only (git mutations, docker); tool-specific rules belong to the toolchain profile/recipe or the pipeline author. Command steps are unaffected. See [Banned commands](#banned-commands-forbidden_commands). |
|
|
227
229
|
| `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. |
|
|
228
230
|
| `llms:` (top-level) / step `llm:` | Named LLM profiles, each a full `LlmConfig` (`model`, `base_url`, `api_key_env`, `timeout`, `reasoning_effort`). A step names one with `llm: <profile-name>`; the whole profile moves together (no partial merges). Allowed only on agent and brief steps. The top-level `llm:` block remains the default. Profile keys are host-side - sandbox access still goes through `secrets:`. |
|
|
229
231
|
| `reasoning_effort:` | Optional `low` / `medium` / `high` / `xhigh` / `none`. Set on the `llm` block, a `llms:` profile, or per-step (a step value overrides the effective profile's). Unset anywhere = reasoning off: nothing is forwarded and reasoning is stripped from requests. A thinking model without an effort level is invalid config and fails at the first request (crap in, crap out). See [Reasoning effort](#reasoning-effort-opt-in). |
|
|
@@ -240,6 +242,22 @@ steps:
|
|
|
240
242
|
| `caches:` / `sync_excludes:` | Top-level run-wide lists. `caches:` names repo-relative toolchain cache dirs bind-mounted rw from host-side stores under `{state_dir}/caches/<name>` (auto-created); a `setup:` hook can warm them (the provision container carries the same cache mounts); `sync_excludes:` names non-gitignored scratch paths skipped from both the export copy and sync-back (wildcard `*` spans separators, so `**/bin/**` matches a `/bin/` segment at any depth). Both default to `[]`. See [docs/setup.md](docs/setup.md). |
|
|
241
243
|
| `merge_on_conflict:` | Top-level run-wide list of append-style logs (same path vocabulary as `sync_excludes:`, default `[]`). A concurrent host edit to a listed path is union-merged instead of raising: the host file wins and the sandbox's export-only lines are appended in order; both sides converge and the run continues. See [docs/setup.md](docs/setup.md). |
|
|
242
244
|
|
|
245
|
+
### Banned commands (`forbidden_commands:`)
|
|
246
|
+
|
|
247
|
+
`forbidden_commands:` is a per-step list of regex patterns. The sandbox
|
|
248
|
+
terminal checks them against every command the agent runs, before any shell
|
|
249
|
+
spawn: a match returns a structured `[forbidden: <label>]` observation instead
|
|
250
|
+
of executing. The engine defaults are run invariants only - git mutations and
|
|
251
|
+
docker - so the deny-list is network-independent by design: `network:` stays a
|
|
252
|
+
docker/egress concern. A single string is coerced to a one-item list; an
|
|
253
|
+
invalid regex is a load error. Command steps bypass the filter by design.
|
|
254
|
+
|
|
255
|
+
Tool-specific rules are not engine defaults. A toolchain profile or bundled
|
|
256
|
+
recipe declares them per step (for example the `python` recipe denies
|
|
257
|
+
`pip install`, `uv add`, and `pip-compile` on its agent steps), and a
|
|
258
|
+
consumer pipeline authors its own for its toolchain. There is no disable
|
|
259
|
+
escape hatch for the engine defaults.
|
|
260
|
+
|
|
243
261
|
### Reasoning effort (opt-in)
|
|
244
262
|
|
|
245
263
|
`reasoning_effort` is an opt-in latency dial on the pipeline `llm` block, a
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentic-dev-loop"
|
|
7
|
-
version = "0.10.
|
|
7
|
+
version = "0.10.3"
|
|
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-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/forbidden.py
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Banned-command deny-list (036 D1).
|
|
1
|
+
"""Banned-command deny-list (036 D1, revised by 056).
|
|
2
2
|
|
|
3
3
|
The sandbox terminal enforces a deny-list before any shell spawn: a command
|
|
4
4
|
whose argv matches a step's ``forbidden_commands:`` regex (merged with the
|
|
@@ -6,11 +6,15 @@ engine defaults) returns a structured observation instead of reaching the
|
|
|
6
6
|
sandbox. Command steps are unaffected - they are declared infrastructure and
|
|
7
7
|
already fail fast on their exit code.
|
|
8
8
|
|
|
9
|
+
The defaults are run invariants only: git mutations and docker.
|
|
10
|
+
Tool-specific rules are not engine defaults - a step declares them with
|
|
11
|
+
``forbidden_commands`` (the bundled onboarding recipes carry the engine's
|
|
12
|
+
own toolchain guidance). The deny-list is network-independent by design;
|
|
13
|
+
network is a sandbox egress concern.
|
|
14
|
+
|
|
9
15
|
The default list carries human-readable labels; custom entries use their
|
|
10
|
-
regex text as the label.
|
|
11
|
-
gated: denied only while the step's ``network:`` flag is off.
|
|
16
|
+
regex text as the label.
|
|
12
17
|
"""
|
|
13
|
-
|
|
14
18
|
from __future__ import annotations
|
|
15
19
|
|
|
16
20
|
import re
|
|
@@ -30,22 +34,9 @@ _DEFAULT_PATTERNS: tuple[tuple[str, str], ...] = (
|
|
|
30
34
|
("git checkout", r"\bgit\s+checkout\b"),
|
|
31
35
|
("git reset", r"\bgit\s+reset\b"),
|
|
32
36
|
("git tag", r"\bgit\s+tag\b"),
|
|
33
|
-
("npm install", r"\bnpm\s+install\b"),
|
|
34
|
-
("npm ci", r"\bnpm\s+ci\b"),
|
|
35
|
-
("pip install", r"\bpip\s+install\b"),
|
|
36
|
-
("uv add", r"\buv\s+add\b"),
|
|
37
|
-
("pip-compile", r"\bpip-compile\b"),
|
|
38
|
-
("dotnet add package", r"\bdotnet\s+add\s+package\b"),
|
|
39
|
-
("npx playwright", r"\bnpx\s+playwright\b"),
|
|
40
|
-
("playwright", r"\bplaywright\b"),
|
|
41
37
|
("docker", r"\bdocker\b"),
|
|
42
38
|
)
|
|
43
39
|
|
|
44
|
-
_NETWORK_GATED_PATTERNS: tuple[tuple[str, str], ...] = (
|
|
45
|
-
("dotnet restore", r"\bdotnet\s+restore\b"),
|
|
46
|
-
("dotnet add", r"\bdotnet\s+add\b"),
|
|
47
|
-
)
|
|
48
|
-
|
|
49
40
|
|
|
50
41
|
def _compile(label: str, pattern: str) -> ForbiddenRule:
|
|
51
42
|
return ForbiddenRule(label=label, regex=re.compile(pattern, re.IGNORECASE))
|
|
@@ -54,21 +45,16 @@ def _compile(label: str, pattern: str) -> ForbiddenRule:
|
|
|
54
45
|
DEFAULT_FORBIDDEN_RULES: tuple[ForbiddenRule, ...] = tuple(
|
|
55
46
|
_compile(label, pattern) for label, pattern in _DEFAULT_PATTERNS)
|
|
56
47
|
|
|
57
|
-
NETWORK_GATED_FORBIDDEN_RULES: tuple[ForbiddenRule, ...] = tuple(
|
|
58
|
-
_compile(label, pattern) for label, pattern in _NETWORK_GATED_PATTERNS)
|
|
59
|
-
|
|
60
48
|
|
|
61
|
-
def resolve_forbidden_rules(
|
|
62
|
-
|
|
49
|
+
def resolve_forbidden_rules(
|
|
50
|
+
custom_patterns: list[str] | tuple[str, ...] | None,
|
|
51
|
+
) -> tuple[ForbiddenRule, ...]:
|
|
63
52
|
"""Merge the engine defaults with a step's custom regex list.
|
|
64
53
|
|
|
65
54
|
Custom entries are appended after the defaults, so the first match wins
|
|
66
|
-
on overlap
|
|
67
|
-
off; there is no disable escape hatch for the engine defaults.
|
|
55
|
+
on overlap; there is no disable escape hatch for the engine defaults.
|
|
68
56
|
"""
|
|
69
57
|
rules = list(DEFAULT_FORBIDDEN_RULES)
|
|
70
|
-
if not network:
|
|
71
|
-
rules.extend(NETWORK_GATED_FORBIDDEN_RULES)
|
|
72
58
|
for pattern in custom_patterns or ():
|
|
73
59
|
rules.append(_compile(pattern, pattern))
|
|
74
60
|
return tuple(rules)
|
|
@@ -14,7 +14,18 @@ dev_impossible_set:
|
|
|
14
14
|
- "database-category tests"
|
|
15
15
|
- "commands that boot servers or need external services"
|
|
16
16
|
steps:
|
|
17
|
-
-
|
|
17
|
+
- name: brief
|
|
18
|
+
kind: brief
|
|
19
|
+
brief_inputs: {include: [AGENTS.md]}
|
|
20
|
+
forbidden_commands:
|
|
21
|
+
- npm install
|
|
22
|
+
- npm ci
|
|
23
|
+
- dotnet add package
|
|
24
|
+
- dotnet restore
|
|
25
|
+
- dotnet add
|
|
26
|
+
- npx playwright
|
|
27
|
+
- playwright
|
|
28
|
+
on_pass: dev
|
|
18
29
|
- name: dev
|
|
19
30
|
kind: agent
|
|
20
31
|
agent: dev
|
|
@@ -22,6 +33,14 @@ steps:
|
|
|
22
33
|
max_iterations: 100
|
|
23
34
|
finalize_nudge_turns: 10
|
|
24
35
|
produces: implementation-summary.md
|
|
36
|
+
forbidden_commands:
|
|
37
|
+
- npm install
|
|
38
|
+
- npm ci
|
|
39
|
+
- dotnet add package
|
|
40
|
+
- dotnet restore
|
|
41
|
+
- dotnet add
|
|
42
|
+
- npx playwright
|
|
43
|
+
- playwright
|
|
25
44
|
setup: 'cd __FRONTEND_DIR__ && npm ci && cd .. && dotnet restore __SOLUTION__'
|
|
26
45
|
mounts: [{path: ".", mode: rw}]
|
|
27
46
|
on_pass: restore
|
|
@@ -62,9 +81,23 @@ steps:
|
|
|
62
81
|
optional: {detect: tools/run-conformance.sh}
|
|
63
82
|
on_pass: review
|
|
64
83
|
on_fail: {goto: dev, retry: true}
|
|
65
|
-
-
|
|
66
|
-
|
|
67
|
-
|
|
84
|
+
- name: review
|
|
85
|
+
kind: agent
|
|
86
|
+
agent: review
|
|
87
|
+
reasoning_effort: xhigh
|
|
88
|
+
artifact: review-findings.md
|
|
89
|
+
verdicts: {pass: APPROVED, fail: NEEDS CHANGES}
|
|
90
|
+
mounts: [{path: ".", mode: ro}]
|
|
91
|
+
forbidden_commands:
|
|
92
|
+
- npm install
|
|
93
|
+
- npm ci
|
|
94
|
+
- dotnet add package
|
|
95
|
+
- dotnet restore
|
|
96
|
+
- dotnet add
|
|
97
|
+
- npx playwright
|
|
98
|
+
- playwright
|
|
99
|
+
on_pass: qa
|
|
100
|
+
on_fail: {goto: dev, retry: true}
|
|
68
101
|
- name: qa
|
|
69
102
|
kind: agent
|
|
70
103
|
agent: qa
|
|
@@ -11,7 +11,14 @@ brief_env_facts:
|
|
|
11
11
|
dev_impossible_set:
|
|
12
12
|
- "package-manager fetches after a new pin run only in the network-on restore step"
|
|
13
13
|
steps:
|
|
14
|
-
-
|
|
14
|
+
- name: brief
|
|
15
|
+
kind: brief
|
|
16
|
+
brief_inputs: {include: [AGENTS.md]}
|
|
17
|
+
forbidden_commands:
|
|
18
|
+
- pip install
|
|
19
|
+
- uv add
|
|
20
|
+
- pip-compile
|
|
21
|
+
on_pass: dev
|
|
15
22
|
- name: dev
|
|
16
23
|
kind: agent
|
|
17
24
|
agent: dev
|
|
@@ -19,6 +26,10 @@ steps:
|
|
|
19
26
|
max_iterations: 150
|
|
20
27
|
finalize_nudge_turns: 10
|
|
21
28
|
produces: implementation-summary.md
|
|
29
|
+
forbidden_commands:
|
|
30
|
+
- pip install
|
|
31
|
+
- uv add
|
|
32
|
+
- pip-compile
|
|
22
33
|
setup: 'python -m venv __VENV__ && __VENV__/bin/pip install -e "__DEV_EXTRAS__"'
|
|
23
34
|
mounts: [{path: ".", mode: rw}]
|
|
24
35
|
on_pass: restore
|
|
@@ -38,9 +49,19 @@ steps:
|
|
|
38
49
|
mounts: [{path: ".", mode: rw}]
|
|
39
50
|
on_pass: review
|
|
40
51
|
on_fail: {goto: dev, retry: true}
|
|
41
|
-
-
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
- name: review
|
|
53
|
+
kind: agent
|
|
54
|
+
agent: review
|
|
55
|
+
reasoning_effort: xhigh
|
|
56
|
+
artifact: review-findings.md
|
|
57
|
+
verdicts: {pass: APPROVED, fail: NEEDS CHANGES}
|
|
58
|
+
mounts: [{path: ".", mode: ro}]
|
|
59
|
+
forbidden_commands:
|
|
60
|
+
- pip install
|
|
61
|
+
- uv add
|
|
62
|
+
- pip-compile
|
|
63
|
+
on_pass: qa
|
|
64
|
+
on_fail: {goto: dev, retry: true}
|
|
44
65
|
- name: qa
|
|
45
66
|
kind: agent
|
|
46
67
|
agent: qa
|
|
@@ -158,8 +158,7 @@ class Runtime:
|
|
|
158
158
|
else:
|
|
159
159
|
cwd, mount_set, egress_policy = (self.workspace, None, None)
|
|
160
160
|
forbidden_commands = (
|
|
161
|
-
resolve_forbidden_rules(step.forbidden_commands
|
|
162
|
-
step.network)
|
|
161
|
+
resolve_forbidden_rules(step.forbidden_commands)
|
|
163
162
|
if step is not None else None)
|
|
164
163
|
allowlist = (
|
|
165
164
|
tuple(self.engine_allowlist)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentic-dev-loop
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.3
|
|
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
|
|
@@ -229,6 +229,7 @@ steps:
|
|
|
229
229
|
agent: dev
|
|
230
230
|
produces: implementation-summary.md
|
|
231
231
|
terminal_timeout: 900
|
|
232
|
+
# forbidden_commands: ["npm install", "npm ci"] # per-step tool deny-list; engine defaults are git mutations + docker
|
|
232
233
|
on_pass: test
|
|
233
234
|
|
|
234
235
|
- name: test
|
|
@@ -268,6 +269,7 @@ steps:
|
|
|
268
269
|
| `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. |
|
|
269
270
|
| `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. |
|
|
270
271
|
| `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). |
|
|
272
|
+
| `forbidden_commands:` | Optional per-step list of regex patterns. The sandbox terminal matches them against each agent command before any shell spawn; a match returns a structured `[forbidden: <label>]` observation instead of running. A single string is coerced to a one-item list; an invalid regex fails at load. Defaults are run invariants only (git mutations, docker); tool-specific rules belong to the toolchain profile/recipe or the pipeline author. Command steps are unaffected. See [Banned commands](#banned-commands-forbidden_commands). |
|
|
271
273
|
| `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. |
|
|
272
274
|
| `llms:` (top-level) / step `llm:` | Named LLM profiles, each a full `LlmConfig` (`model`, `base_url`, `api_key_env`, `timeout`, `reasoning_effort`). A step names one with `llm: <profile-name>`; the whole profile moves together (no partial merges). Allowed only on agent and brief steps. The top-level `llm:` block remains the default. Profile keys are host-side - sandbox access still goes through `secrets:`. |
|
|
273
275
|
| `reasoning_effort:` | Optional `low` / `medium` / `high` / `xhigh` / `none`. Set on the `llm` block, a `llms:` profile, or per-step (a step value overrides the effective profile's). Unset anywhere = reasoning off: nothing is forwarded and reasoning is stripped from requests. A thinking model without an effort level is invalid config and fails at the first request (crap in, crap out). See [Reasoning effort](#reasoning-effort-opt-in). |
|
|
@@ -284,6 +286,22 @@ steps:
|
|
|
284
286
|
| `caches:` / `sync_excludes:` | Top-level run-wide lists. `caches:` names repo-relative toolchain cache dirs bind-mounted rw from host-side stores under `{state_dir}/caches/<name>` (auto-created); a `setup:` hook can warm them (the provision container carries the same cache mounts); `sync_excludes:` names non-gitignored scratch paths skipped from both the export copy and sync-back (wildcard `*` spans separators, so `**/bin/**` matches a `/bin/` segment at any depth). Both default to `[]`. See [docs/setup.md](docs/setup.md). |
|
|
285
287
|
| `merge_on_conflict:` | Top-level run-wide list of append-style logs (same path vocabulary as `sync_excludes:`, default `[]`). A concurrent host edit to a listed path is union-merged instead of raising: the host file wins and the sandbox's export-only lines are appended in order; both sides converge and the run continues. See [docs/setup.md](docs/setup.md). |
|
|
286
288
|
|
|
289
|
+
### Banned commands (`forbidden_commands:`)
|
|
290
|
+
|
|
291
|
+
`forbidden_commands:` is a per-step list of regex patterns. The sandbox
|
|
292
|
+
terminal checks them against every command the agent runs, before any shell
|
|
293
|
+
spawn: a match returns a structured `[forbidden: <label>]` observation instead
|
|
294
|
+
of executing. The engine defaults are run invariants only - git mutations and
|
|
295
|
+
docker - so the deny-list is network-independent by design: `network:` stays a
|
|
296
|
+
docker/egress concern. A single string is coerced to a one-item list; an
|
|
297
|
+
invalid regex is a load error. Command steps bypass the filter by design.
|
|
298
|
+
|
|
299
|
+
Tool-specific rules are not engine defaults. A toolchain profile or bundled
|
|
300
|
+
recipe declares them per step (for example the `python` recipe denies
|
|
301
|
+
`pip install`, `uv add`, and `pip-compile` on its agent steps), and a
|
|
302
|
+
consumer pipeline authors its own for its toolchain. There is no disable
|
|
303
|
+
escape hatch for the engine defaults.
|
|
304
|
+
|
|
287
305
|
### Reasoning effort (opt-in)
|
|
288
306
|
|
|
289
307
|
`reasoning_effort` is an opt-in latency dial on the pipeline `llm` block, a
|
|
@@ -48,34 +48,21 @@ def _export(tmp_path: Path, ws: Path) -> Path:
|
|
|
48
48
|
|
|
49
49
|
class TestForbiddenCommandRules:
|
|
50
50
|
def test_defaults_cover_deny_list(self):
|
|
51
|
-
rules = resolve_forbidden_rules([]
|
|
51
|
+
rules = resolve_forbidden_rules([])
|
|
52
52
|
labels = [r.label for r in rules]
|
|
53
|
-
|
|
54
|
-
"git reset", "git tag", "npm install", "npm ci",
|
|
55
|
-
"pip install", "uv add", "pip-compile",
|
|
56
|
-
"dotnet add package", "npx playwright", "playwright",
|
|
57
|
-
"docker", "dotnet restore", "dotnet add"):
|
|
58
|
-
assert expected in labels
|
|
59
|
-
|
|
60
|
-
def test_network_on_drops_network_gated_dotnet(self):
|
|
61
|
-
offline = resolve_forbidden_rules([], network=False)
|
|
62
|
-
online = resolve_forbidden_rules([], network=True)
|
|
63
|
-
assert "dotnet restore" in [r.label for r in offline]
|
|
64
|
-
assert "dotnet add" in [r.label for r in offline]
|
|
65
|
-
assert "dotnet restore" not in [r.label for r in online]
|
|
66
|
-
assert "dotnet add" not in [r.label for r in online]
|
|
53
|
+
assert labels == ['git commit', 'git push', 'git rebase', 'git checkout', 'git reset', 'git tag', 'docker']
|
|
67
54
|
|
|
68
55
|
def test_custom_rules_append_with_pattern_label(self):
|
|
69
|
-
rules = resolve_forbidden_rules([r"rm\s+-rf"]
|
|
56
|
+
rules = resolve_forbidden_rules([r"rm\s+-rf"])
|
|
70
57
|
assert rules[-1].label == r"rm\s+-rf"
|
|
71
58
|
|
|
72
59
|
def test_shell_wrapped_argv_matches(self):
|
|
73
60
|
argv = ["/bin/sh", "-c", "git push origin main"]
|
|
74
61
|
assert match_forbidden_command(
|
|
75
|
-
argv, resolve_forbidden_rules([]
|
|
62
|
+
argv, resolve_forbidden_rules([])) == "git push"
|
|
76
63
|
|
|
77
64
|
def test_empty_argv_never_matches(self):
|
|
78
|
-
assert match_forbidden_command([], resolve_forbidden_rules([]
|
|
65
|
+
assert match_forbidden_command([], resolve_forbidden_rules([])) is None
|
|
79
66
|
|
|
80
67
|
|
|
81
68
|
class TestSandboxTerminalDenyList:
|
|
@@ -86,7 +73,7 @@ class TestSandboxTerminalDenyList:
|
|
|
86
73
|
from agentic_dev_loop.engine.agents import SandboxTerminalExecutor
|
|
87
74
|
ex = SandboxTerminalExecutor(
|
|
88
75
|
fake_sandbox, workspace=str(tmp_path), env={},
|
|
89
|
-
forbidden_commands=resolve_forbidden_rules([]
|
|
76
|
+
forbidden_commands=resolve_forbidden_rules([]))
|
|
90
77
|
obs = ex(TerminalAction(command="git commit -m x"), None)
|
|
91
78
|
assert "[forbidden: git commit]" in obs.text
|
|
92
79
|
assert obs.exit_code is None
|
|
@@ -98,34 +85,11 @@ class TestSandboxTerminalDenyList:
|
|
|
98
85
|
from agentic_dev_loop.engine.agents import SandboxTerminalExecutor
|
|
99
86
|
ex = SandboxTerminalExecutor(
|
|
100
87
|
fake_sandbox, workspace=str(tmp_path), env={},
|
|
101
|
-
forbidden_commands=resolve_forbidden_rules([r"rm\s+-rf"]
|
|
88
|
+
forbidden_commands=resolve_forbidden_rules([r"rm\s+-rf"]))
|
|
102
89
|
obs = ex(TerminalAction(command="rm -rf /tmp/x"), None)
|
|
103
90
|
assert "[forbidden: rm\\s+-rf]" in obs.text
|
|
104
91
|
assert fake_sandbox.requests == []
|
|
105
92
|
|
|
106
|
-
def test_dotnet_restore_denied_offline(self, fake_sandbox, tmp_path):
|
|
107
|
-
from openhands.tools.terminal import TerminalAction
|
|
108
|
-
|
|
109
|
-
from agentic_dev_loop.engine.agents import SandboxTerminalExecutor
|
|
110
|
-
ex = SandboxTerminalExecutor(
|
|
111
|
-
fake_sandbox, workspace=str(tmp_path), env={},
|
|
112
|
-
forbidden_commands=resolve_forbidden_rules([], network=False))
|
|
113
|
-
obs = ex(TerminalAction(command="dotnet restore"), None)
|
|
114
|
-
assert "[forbidden: dotnet restore]" in obs.text
|
|
115
|
-
assert fake_sandbox.requests == []
|
|
116
|
-
|
|
117
|
-
def test_dotnet_restore_allowed_online(self, fake_sandbox, tmp_path):
|
|
118
|
-
from openhands.tools.terminal import TerminalAction
|
|
119
|
-
|
|
120
|
-
from agentic_dev_loop.engine.agents import SandboxTerminalExecutor
|
|
121
|
-
ex = SandboxTerminalExecutor(
|
|
122
|
-
fake_sandbox, workspace=str(tmp_path), env={}, network=True,
|
|
123
|
-
forbidden_commands=resolve_forbidden_rules([], network=True))
|
|
124
|
-
fake_sandbox.script(lambda req: (0, "restored", False))
|
|
125
|
-
obs = ex(TerminalAction(command="dotnet restore"), None)
|
|
126
|
-
assert obs.exit_code == 0
|
|
127
|
-
assert fake_sandbox.requests[0].argv == ["dotnet", "restore"]
|
|
128
|
-
|
|
129
93
|
|
|
130
94
|
class TestPathAllowed:
|
|
131
95
|
def test_literal_prefix(self):
|
|
@@ -47,6 +47,9 @@ def test_python_recipe_gate_set_and_commands():
|
|
|
47
47
|
assert steps["qa"]["finalize_nudge_turns"] == 10
|
|
48
48
|
assert "env" in steps["qa"]["verdicts"]
|
|
49
49
|
assert "on_env" not in steps["qa"]
|
|
50
|
+
for name in ("brief", "dev", "review"):
|
|
51
|
+
assert steps[name]["forbidden_commands"] == ['pip install', 'uv add', 'pip-compile']
|
|
52
|
+
assert "forbidden_commands" not in steps["qa"]
|
|
50
53
|
|
|
51
54
|
|
|
52
55
|
def test_dotnet_node_recipe_gate_set_and_commands():
|
|
@@ -69,6 +72,9 @@ def test_dotnet_node_recipe_gate_set_and_commands():
|
|
|
69
72
|
assert steps["qa"]["env"] == {"PLAYWRIGHT_BROWSERS_PATH": "qa/.browsers"}
|
|
70
73
|
assert steps["qa"]["mounts"] == [{"path": ".", "mode": "rw"}]
|
|
71
74
|
assert len(doc["dev_impossible_set"]) == 4
|
|
75
|
+
for name in ("brief", "dev", "review"):
|
|
76
|
+
assert steps[name]["forbidden_commands"] == ['npm install', 'npm ci', 'dotnet add package', 'dotnet restore', 'dotnet add', 'npx playwright', 'playwright']
|
|
77
|
+
assert "forbidden_commands" not in steps["qa"]
|
|
72
78
|
|
|
73
79
|
|
|
74
80
|
def test_unknown_recipe_raises():
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/core/profiles/__init__.py
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-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/agent_error_guide.md
RENAMED
|
File without changes
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/agent_error_guide.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/allowlist.py
RENAMED
|
File without changes
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/brief_coverage.py
RENAMED
|
File without changes
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/brief_prompt.md
RENAMED
|
File without changes
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/brief_prompt.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/containers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/egress_policy.py
RENAMED
|
File without changes
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/egress_proxy.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/extensions/aidlc.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/hostboundary.py
RENAMED
|
File without changes
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/isolation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/onboarding/checks.py
RENAMED
|
File without changes
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/onboarding/doctor.py
RENAMED
|
File without changes
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/onboarding/init.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
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/seccomp/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/step_workspace.py
RENAMED
|
File without changes
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop/engine/sync_back.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/src/agentic_dev_loop.egg-info/requires.txt
RENAMED
|
File without changes
|
{agentic_dev_loop-0.10.2 → agentic_dev_loop-0.10.3}/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
|
|
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
|
|
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
|