feat(agents): force agents to the Makefile — deny raw uv/pip/conda/poetry (CEO #15) (#518)

* fix(prompts): point agents at Makefile, drop raw uv run instructions

backend.md:23-26 literally instructed raw uv run ruff/mypy/pytest (copied from
the human-facing CLAUDE.md), so agents bypassed the Makefile's UV_NO_SYNC=1 +
private UV_CACHE_DIR venv-corruption guard. Replace with make targets across
backend/developer/qa/cell_pm + a universal rule in base.md. Regenerate verbs.md
from the updated regen script (baked instruction now make foundation-check) and
align the Makefile drift message. Ships with the bash-guard deny in the next
commit so agents don't loop fighting the guard.

* feat(bash-guard): deny raw uv/pip/conda/poetry, point at Makefile

When a Makefile is present, deny raw uv run/uv pip/uv lock/add/remove, pip/pip3
install/uninstall, conda install/create/run, poetry run/install/add and remediate
to make quality/gate/lint/test. Skipped when no Makefile (Makefile-less projects
not blocked). ROBOCO_GUARD_SKIP_PM=1 (grok path) nudges exit 0 instead of the
run-canceling exit 2. Overrides the prior bare-uv-run-allowed stance by CEO
direction; the /app-targeted blocks above keep priority.

* feat(grok): deny raw uv/pip/conda/poetry via native --deny + PM-skip nudge

Add _RAW_PM_DENY (uv run/pip install/lock/add/remove, pip/pip3 install, conda
install/create/run, poetry run/install/add) to _deny_rules so grok's graceful
native --deny blocks raw package-manager commands (model adapts to make, run
continues — unlike a hook deny which cancels the run). The bash-guard hook
keeps the compound-command fallback (cd x && uv run) and nudges exit 0 there via
ROBOCO_GUARD_SKIP_PM=1 in the grok hook env, never canceling.

* test(bash-guard): align existing tests with W1 Makefile-gate policy

Raw uv run / pip install are now Makefile-gated (W1, CEO item #15), so two
existing bash-guard invariants reverse:

- test_allows_pytest_even_if_suite_uses_requests keeps its HTTP-injection
  allow-path intent but uses bare `python -m pytest` (raw `uv run` is now
  denied); the deny case is covered by test_bash_guard_makefile_guardrail.
- test_allows_pip_install_in_workspace -> test_denies_pip_install_when_makefile_
  present: a workspace clone carries a Makefile, so bare pip install is now
  denied -> agents use `make` / `uv sync --extra dev`. Makefile-less skips
  stay covered.

Gate: 12994 passed, 439 skipped, 94.81% cov (DB env :55432 user renzof);
the lone flaky integration error passes in isolation (DB-state race, not W1).

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
Renzo F
2026-07-15 04:32:27 +02:00
committed by GitHub
co-authored by Renn F
parent fe9940dec1
commit a9dee3b34e
14 changed files with 238 additions and 17 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
# Per-role verb shapes (autogenerated)
Run `uv run python scripts/regenerate_verb_tables.py` after changing
Run `make foundation-check` after changing
any role config or schema. Role prompts reference this file's sections
as the source of truth for verb signatures.
+4
View File
@@ -87,3 +87,7 @@ The briefing also carries `company_goals` — the company's charter (north star,
## Substitute reasons (for `i_am_blocked`)
`low_context`, `out_of_scope_team`, `out_of_scope_role`, `task_complete`, `max_retries`, `blocked_external`.
## Makefile is the gate surface
Run `make <target>` for lint/test/typecheck — never raw `uv run`/`pip`/`conda`/`poetry`. The Makefile sets `UV_NO_SYNC=1` and a private `UV_CACHE_DIR` to prevent the concurrent-venv-corruption race; bare `uv run` bypasses both. Targets: `make quality` (full gate), `make gate` (fast pre-submit), `make lint`, `make test`, `make panel-gate` (frontend).
+1 -1
View File
@@ -177,7 +177,7 @@ The PM journal is what makes the cell legible to Main PM and CEO. Skipping entri
1. ✅ Every subtask under your task is in a terminal state (`completed` or `cancelled`) — gateway-enforced.
2. ✅ You inspected each child's PR (already merged into your branch via `complete`) — call `evidence(your_task_id)` for the aggregate diff.
3. ✅ Each acceptance criterion on YOUR cell-PM task is met by something in the aggregate (commit / merged PR / doc).
4. ✅ Tests/lint on the aggregate are green — your branch is the integration point for the cell, so run `make quality` (or equivalent) before submitting up.
4. ✅ Tests/lint on the aggregate are green — your branch is the integration point for the cell, so run `make quality` before submitting up.
5. ✅ `note(scope='reflect', task_id=...)` written — aggregate review.
6. ✅ `note(scope='decision', task_id=...)` written — submit-up rationale (gateway-required).
7. ✅ `notes` argument to `submit_up` >= 20 chars (gateway-enforced).
+1 -1
View File
@@ -103,7 +103,7 @@ The gateway enforces some of these; the rest are convention but failing one of t
1. ✅ At least one `commit()` on this branch (gateway-enforced).
2. ✅ Every acceptance criterion is met by actual code or test, not just intention. Re-read them via `evidence(task_id)`.
3. ✅ Tests/lint/typecheck pass locally — run them via `Bash`. If your project has `make quality` (or equivalent), run it. **`i_am_done` runs the fast gate (lint + typecheck) in your workspace and rejects the submit if it's red** — so run it yourself first and submit green on the first try; QA and CI run the full gate (incl. tests) too.
3. ✅ Tests/lint/typecheck pass locally — run `make quality` (or `make gate` for the fast pre-submit gate). Never raw `uv run`. **`i_am_done` runs the fast gate (lint + typecheck) in your workspace and rejects the submit if it's red** — so run it yourself first and submit green on the first try; QA and CI run the full gate (incl. tests) too.
4. ✅ `git diff` (call `evidence(task_id)` to inspect) shows nothing stray — no `print()` debugging, no commented-out code, no unrelated edits.
5. ✅ `note(scope='reflect', task_id=...)` walks through every criterion (gateway-enforced as `journal:reflect`).
6. ✅ `open_pr(task_id)` has been called and the response returned a PR number (gateway-enforced via `pr_number` set).
+1 -1
View File
@@ -51,7 +51,7 @@ A pass without evidence is a betrayal of your role: the entire downstream chain
3. If you need to re-inspect anything, call `evidence(task_id)`. **Do not** grep the workspace or run `Bash git diff` — the diff is in the response.
4. **Read the dev's `reflect` note** — it walks through every acceptance criterion and explains how each is met. Cross-check those claims against the actual diff.
5. For each acceptance criterion individually: confirm there is a referencing artifact (commit, progress entry, or file change) AND that the change actually meets it. Don't batch-approve criteria; check them one at a time.
6. Run tests/lint via `Bash` (e.g. `make quality` or `pytest`) — even if the dev says they passed, you re-run.
6. Run `make quality` — even if the dev says they passed, you re-run.
7. `note(scope='struggle', text='...')` if you can't decide — flag the ambiguity rather than guess. Then `dm(recipient=<dev>, text='<question>')` to ask before failing.
8. `note(scope='learning', text="<what worked / what would have caught the issue earlier / what pattern this work establishes>")` — required before pass/fail.
9. Pass: `pass(task_id, notes="<>=80 chars: overall review summary, edge cases tested, any caveats>", ac_verdicts=["criterion 1 — verified by <commit/file/line>", "criterion 2 — verified by <artifact>", ...])`**one entry per acceptance criterion, in the task's criterion order**; the gateway rejects a pass that leaves any criterion uncovered. If even one criterion does not hold, do NOT pass — `fail` instead. Fail: `fail(task_id, findings=[{"file": "path", "line": 42, "severity": "major", "criterion": "<ac id if applicable>", "expected": "...", "actual": "..."}, ...])` — one object per issue, capped at 10 (nudge above 5 — split or prioritize).
+7 -5
View File
@@ -19,11 +19,13 @@
## Development Standards
```bash
# Before any commit
uv run ruff format .
uv run ruff check .
uv run mypy roboco/
uv run pytest
# Before any commit — use the Makefile, never raw `uv run`/`pip`/`conda`/`poetry`.
# The Makefile sets UV_NO_SYNC=1 + a private UV_CACHE_DIR to prevent venv
# corruption; bare `uv run` bypasses both.
make lint # ruff format + ruff check + mypy + vulture (formats in place)
make gate # fast pre-submit: ruff format --check + ruff check + mypy + xenon
make quality # full merge gate (lint+types+tests+cov+xenon+bandit+audit+...)
make test # pytest with coverage
# Coverage target: 80%
```