Fix audit tool bootstrap and add per-run preflight

audit-code install-tools.sh:
- Buffer the opengrep release JSON before grep -m1; curl died with (23)
  under pipefail when grep quit early.
- Use ${m}: in the PowerShell block; $m: parsed as a scope-qualified var.
- On Arch, skip paru/yay when pacman -Q shows every package installed,
  since --needed still invokes sudo.
- Add --check-only (fast, installs nothing, non-zero naming missing tools)
  and --user-only (no system package managers, no sudo).

log-run.py (both skills): put the skill dir on sys.path so running it as
a script from any cwd no longer raises ModuleNotFoundError.

audit-terraform: move deps from requirements.txt into pyproject
dependency groups and add scripts/install-tools.sh (uv sync --group tools,
then check trivy, tflint, tofu, terragrunt, gh).

Both SKILL.md files gain a 0.5 Preflight step and call scripts through
uv run --project ${SKILL_DIR}. tools_unavailable is now a map of tool to
exact install command; audit-terraform skips trivy when absent and stops
with an install hint instead of crashing when tofu/terragrunt is missing.
This commit is contained in:
2026-09-22 15:21:28 -05:00
parent d3258b224a
commit 37fc3fb291
30 changed files with 1045 additions and 84 deletions
@@ -123,6 +123,7 @@ def test_cli_records_tool_unavailable(tmp_path):
assert rc == 0
manifest = json.loads((out_dir / "manifest.json").read_text())
assert "bandit" in manifest["tools_unavailable"]
assert manifest["tools_unavailable"]["bandit"].endswith("scripts/install-tools.sh --user-only")
def test_cli_alerts_on_unsupported_languages(tmp_path):
@@ -0,0 +1,127 @@
"""Tests for scripts/install-tools.sh, run against a copy with stubbed binaries."""
from __future__ import annotations
import os
import re
import shutil
import subprocess
from pathlib import Path
import pytest
_SCRIPT = Path(__file__).resolve().parent.parent / "scripts" / "install-tools.sh"
_VENV_TOOLS = ("bandit", "ruff", "mypy", "pip-audit", "vulture", "radon", "interrogate", "lizard", "opengrep")
_NATIVE_TOOLS = ("gitleaks", "osv-scanner", "gh")
def _skill_copy(tmp_path: Path) -> Path:
skill = tmp_path / "skill"
(skill / "scripts").mkdir(parents=True)
shutil.copy(_SCRIPT, skill / "scripts" / "install-tools.sh")
return skill / "scripts" / "install-tools.sh"
def _stub(bin_dir: Path, name: str, body: str) -> None:
bin_dir.mkdir(parents=True, exist_ok=True)
path = bin_dir / name
path.write_text("#!/usr/bin/env bash\n" + body)
path.chmod(0o755)
def _run(cmd: str, bin_dir: Path, **env: str) -> subprocess.CompletedProcess:
return subprocess.run(
["bash", "-c", cmd],
capture_output=True, text=True, timeout=60, check=False,
env={**os.environ, "PATH": f"{bin_dir}:/usr/bin:/bin", **env},
)
def test_opengrep_tag_lookup_survives_large_release_body(tmp_path: Path) -> None:
script = _skill_copy(tmp_path)
bin_dir = tmp_path / "bin"
log = tmp_path / "curl.log"
_stub(bin_dir, "curl", r'''
for ((i = 1; i <= $#; i++)); do
if [[ "${!i}" == "-o" ]]; then
j=$((i + 1)); echo "$*" >> "$CURL_LOG"; echo bin > "${!j}"; exit 0
fi
done
printf '{\n "tag_name": "v9.9.9",\n "body": "'
head -c 2000000 /dev/zero | tr '\0' x
printf '"\n}\n'
''')
r = _run(f"source {script} && install_opengrep", bin_dir, CURL_LOG=str(log))
assert r.returncode == 0, r.stderr
assert (script.parent.parent / ".venv" / "bin" / "opengrep").is_file()
assert "/releases/download/v9.9.9/" in log.read_text()
@pytest.mark.skipif(shutil.which("pwsh") is None, reason="pwsh not installed")
def test_powershell_module_block_parses() -> None:
block = re.search(r"pwsh -NoProfile -NonInteractive -Command '(.*?)'", _SCRIPT.read_text(), re.DOTALL)
assert block, "embedded pwsh -Command block not found"
check = (
"$errs = $null; "
"[System.Management.Automation.Language.Parser]::ParseInput($env:PS_BLOCK, [ref]$null, [ref]$errs) | Out-Null; "
"$errs | ForEach-Object { $_.Message }; exit $errs.Count"
)
r = subprocess.run(
["pwsh", "-NoProfile", "-NonInteractive", "-Command", check],
capture_output=True, text=True, timeout=60, check=False,
env={**os.environ, "PS_BLOCK": block.group(1)},
)
assert r.returncode == 0, r.stdout + r.stderr
def _arch_stubs(bin_dir: Path, installed: set[str]) -> None:
_stub(bin_dir, "pacman", f'''
[[ "$1" == "-Q" ]] || {{ echo "pacman $*" >> "$CALLS"; exit 1; }}
case "$2" in {"|".join(installed) or "__none__"}) exit 0 ;; *) exit 1 ;; esac
''')
_stub(bin_dir, "paru", 'echo "paru $*" >> "$CALLS"\n')
_stub(bin_dir, "sudo", 'echo "sudo $*" >> "$CALLS"; exit 1\n')
def test_arch_skips_helper_when_everything_installed(tmp_path: Path) -> None:
script = _skill_copy(tmp_path)
bin_dir = tmp_path / "bin"
calls = tmp_path / "calls.log"
_arch_stubs(bin_dir, {"gitleaks", "github-cli", "osv-scanner"})
r = _run(f"source {script} && install_native_arch", bin_dir, CALLS=str(calls))
assert r.returncode == 0, r.stderr
assert not calls.exists(), calls.read_text()
def test_arch_helper_installs_only_missing_packages(tmp_path: Path) -> None:
script = _skill_copy(tmp_path)
bin_dir = tmp_path / "bin"
calls = tmp_path / "calls.log"
_arch_stubs(bin_dir, {"gitleaks", "github-cli"})
r = _run(f"source {script} && install_native_arch", bin_dir, CALLS=str(calls))
assert r.returncode == 0, r.stderr
assert calls.read_text().splitlines() == ["paru -S --needed --noconfirm osv-scanner"]
def test_check_only_on_empty_venv_fails_and_names_missing_tools(tmp_path: Path) -> None:
script = _skill_copy(tmp_path)
bin_dir = tmp_path / "bin"
for tool in _NATIVE_TOOLS:
_stub(bin_dir, tool, "")
r = _run(f"bash {script} --check-only", bin_dir)
assert r.returncode != 0
assert "lizard" in r.stdout
assert "opengrep" in r.stdout
assert not (script.parent.parent / ".venv").exists(), "--check-only must not install anything"
def test_check_only_passes_when_everything_present(tmp_path: Path) -> None:
script = _skill_copy(tmp_path)
venv_bin = script.parent.parent / ".venv" / "bin"
for tool in _VENV_TOOLS:
_stub(venv_bin, tool, "")
bin_dir = tmp_path / "bin"
for tool in _NATIVE_TOOLS:
_stub(bin_dir, tool, "")
r = _run(f"bash {script} --check-only", bin_dir)
assert r.returncode == 0, r.stdout + r.stderr
@@ -3,6 +3,9 @@ from __future__ import annotations
import importlib.util
import json
import os
import subprocess
import sys
from pathlib import Path
_SPEC = importlib.util.spec_from_file_location(
@@ -136,3 +139,17 @@ def test_log_path_parent_is_created(tmp_path: Path) -> None:
])
assert rc == 0
assert log.exists()
def test_runs_as_a_script_from_another_cwd(tmp_path: Path) -> None:
log = tmp_path / "runs.jsonl"
env = {k: v for k, v in os.environ.items() if k != "PYTHONPATH"}
r = subprocess.run(
[sys.executable, str(_SPEC.origin),
"--output-dir", str(tmp_path), "--run-id", "x", "--repo", "/r",
"--mode", "local", "--log-path", str(log), "--usage-json", "-"],
input=json.dumps({"x-reviewer": {"model": "sonnet"}}),
capture_output=True, text=True, cwd=tmp_path, env=env, check=False,
)
assert r.returncode == 0, r.stderr
assert _read_log(log)[0]["agent"] == "x-reviewer"
@@ -46,7 +46,7 @@ def test_manifest_roundtrip():
"csharp": PackageDiff(),
},
tool_stats={},
tools_unavailable=[],
tools_unavailable={},
errors=[],
)
payload = json.loads(m.to_json())
@@ -35,7 +35,7 @@ def _manifest():
],
"package_diffs": {"python": {"added": [], "removed": [], "upgraded": []}},
"tool_stats": {},
"tools_unavailable": [],
"tools_unavailable": {},
"errors": [],
}
@@ -20,7 +20,7 @@ def _manifest_dict():
],
"package_diffs": {"python": {"added": [], "removed": [], "upgraded": []}},
"tool_stats": {},
"tools_unavailable": [],
"tools_unavailable": {},
"errors": [],
}
@@ -96,7 +96,7 @@ def _manifest_with_lua_gha():
],
"package_diffs": {},
"tool_stats": {},
"tools_unavailable": [],
"tools_unavailable": {},
"errors": [],
}
@@ -160,7 +160,7 @@ def _manifest_with_powershell():
],
"package_diffs": {},
"tool_stats": {},
"tools_unavailable": [],
"tools_unavailable": {},
"errors": [],
}