mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
Thread the deployer's product name through the X reply + feature-spotlight prompts (B6 leftover; release/video paths shipped in #570); make the docs-site repo/URL config (ROBOCO_DOCS_SITE_*, defaults unchanged) instead of a roboco-website hardcode (B8); de-assert our repo from the Main PM prompt (B10); derive PR labels from the real target branch instead of literal to-master/to-slave; drop the stale headcount from base.md; and make the bash-guard's Makefile check require an actual quality/gate/lint/ test target before denying raw package-manager commands (no more false-remediation loop on Go/Rust Makefiles). Co-authored-by: Renn F <rennf93@users.noreply.github.com>
107 lines
3.5 KiB
Python
107 lines
3.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 AND declares at least one of the
|
|
quality/gate/lint/test targets, denying raw package-manager / test-runner
|
|
commands and remediating to the make targets. Skipped when no Makefile exists,
|
|
or when one exists but declares none of those targets (a Go/Rust Makefile with
|
|
only build/run — existence alone would remediate into a dead end). 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_skips_deny_when_makefile_lacks_remediation_targets(tmp_path: Path) -> None:
|
|
"""A Go/Rust-style Makefile with only build/run targets — existence alone
|
|
must not deny+remediate to a `make quality`/`gate`/`lint`/`test` that
|
|
doesn't exist (the false-remediation dead-end loop the content check
|
|
closes)."""
|
|
(tmp_path / "Makefile").write_text("build:\n\tgo build ./...\nrun:\n\tgo run .\n")
|
|
rc, _ = _run_hook("uv run pytest", tmp_path)
|
|
assert rc != _DENIED
|
|
|
|
|
|
def test_denies_when_makefile_has_only_one_remediation_target(tmp_path: Path) -> None:
|
|
"""Just one of quality/gate/lint/test is enough to arm the deny — the
|
|
guard doesn't require all four."""
|
|
(tmp_path / "Makefile").write_text("lint:\n\truff check .\n")
|
|
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()
|