mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
87 lines
2.5 KiB
Python
87 lines
2.5 KiB
Python
"""bash-guard Makefile guardrail — deny raw uv/pip/conda/poetry, point at make.
|
|||
|
|
|
||
|
|
CEO item: force agents to the Makefile. The existing hook deliberately allowed
|
||
|
|
bare ``uv run`` (workspace .venv, cwd-relative); this guard overrides that by
|
||
|
|
CEO direction when a ``Makefile`` is present, denying raw package-manager /
|
||
|
|
test-runner commands and remediating to the make targets. Skipped when no
|
||
|
|
Makefile exists so Makefile-less projects aren't blocked. On the grok path
|
||
|
|
(``ROBOCO_GUARD_SKIP_PM=1``) a deny cancels the whole run, so it nudges (exit 0)
|
||
|
|
instead.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import json
|
||
|
|
import os
|
||
|
|
import subprocess
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
REPO_ROOT = Path(__file__).resolve().parents[3]
|
||
|
|
HOOK = REPO_ROOT / "docker" / "scripts" / "bash-guard-hook.sh"
|
||
|
|
|
||
|
|
# Hook exits 2 to deny, 0 to allow. Named (not magic) for ruff PLR2004.
|
||
|
|
_DENIED = 2
|
||
|
|
_ALLOWED = 0
|
||
|
|
|
||
|
|
|
||
|
|
def _run_hook(
|
||
|
|
command: str, cwd: Path, env_over: dict[str, str] | None = None
|
||
|
|
) -> tuple[int, str]:
|
||
|
|
payload = json.dumps({"tool_input": {"command": command}})
|
||
|
|
env = dict(os.environ)
|
||
|
|
if env_over:
|
||
|
|
env.update(env_over)
|
||
|
|
proc = subprocess.run(
|
||
|
|
["bash", str(HOOK)],
|
||
|
|
input=payload,
|
||
|
|
capture_output=True,
|
||
|
|
text=True,
|
||
|
|
cwd=str(cwd),
|
||
|
|
env=env,
|
||
|
|
check=False,
|
||
|
|
)
|
||
|
|
return proc.returncode, proc.stderr
|
||
|
|
|
||
|
|
|
||
|
|
def test_denies_uv_run_when_makefile_present() -> None:
|
||
|
|
rc, err = _run_hook("uv run pytest", REPO_ROOT)
|
||
|
|
assert rc == _DENIED
|
||
|
|
assert "make" in err.lower()
|
||
|
|
|
||
|
|
|
||
|
|
def test_denies_pip_install() -> None:
|
||
|
|
rc, _ = _run_hook("pip install requests", REPO_ROOT)
|
||
|
|
assert rc == _DENIED
|
||
|
|
|
||
|
|
|
||
|
|
def test_denies_compound_uv_run() -> None:
|
||
|
|
rc, _ = _run_hook("cd svc && uv run ruff check .", REPO_ROOT)
|
||
|
|
assert rc == _DENIED
|
||
|
|
|
||
|
|
|
||
|
|
def test_denies_conda_and_poetry() -> None:
|
||
|
|
assert _run_hook("conda install numpy", REPO_ROOT)[0] == _DENIED
|
||
|
|
assert _run_hook("poetry run pytest", REPO_ROOT)[0] == _DENIED
|
||
|
|
|
||
|
|
|
||
|
|
def test_allows_make_quality() -> None:
|
||
|
|
rc, _ = _run_hook("make quality", REPO_ROOT)
|
||
|
|
assert rc != _DENIED
|
||
|
|
|
||
|
|
|
||
|
|
def test_allows_pnpm() -> None:
|
||
|
|
rc, _ = _run_hook("pnpm lint", REPO_ROOT)
|
||
|
|
assert rc != _DENIED
|
||
|
|
|
||
|
|
|
||
|
|
def test_skips_deny_without_makefile(tmp_path: Path) -> None:
|
||
|
|
rc, _ = _run_hook("uv run pytest", tmp_path)
|
||
|
|
assert rc != _DENIED
|
||
|
|
|
||
|
|
|
||
|
|
def test_grok_path_nudges_not_denies() -> None:
|
||
|
|
"""ROBOCO_GUARD_SKIP_PM=1 (grok) -> exit 0 nudge, not run-canceling exit 2."""
|
||
|
|
rc, err = _run_hook("uv run pytest", REPO_ROOT, {"ROBOCO_GUARD_SKIP_PM": "1"})
|
||
|
|
assert rc == _ALLOWED
|
||
|
|
assert "make" in err.lower()
|