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
@@ -3,24 +3,25 @@
set -euo pipefail
SKILL_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$SKILL_DIR"
say() { printf '\n\033[1m▶ %s\033[0m\n' "$*"; }
warn() { printf '\033[33m! %s\033[0m\n' "$*"; }
if ! command -v uv >/dev/null 2>&1; then
warn "uv not installed. Install: https://docs.astral.sh/uv/getting-started/installation/"
warn "Falling back to plain pip — tools will install into the active environment."
if ! command -v pip >/dev/null 2>&1; then
echo "Neither uv nor pip available. Aborting Python-tools install."
exit 1
install_python_tools() {
if ! command -v uv >/dev/null 2>&1; then
warn "uv not installed. Install: https://docs.astral.sh/uv/getting-started/installation/"
warn "Falling back to plain pip — tools will install into the active environment."
if ! command -v pip >/dev/null 2>&1; then
echo "Neither uv nor pip available. Aborting Python-tools install."
exit 1
fi
pip install bandit ruff mypy pip-audit
else
say "Installing Python tools into $SKILL_DIR/.venv/ via uv"
uv sync --group tools
fi
pip install bandit ruff mypy pip-audit
else
say "Installing Python tools into $SKILL_DIR/.venv/ via uv"
uv sync --group tools
fi
}
install_opengrep() {
@@ -42,8 +43,10 @@ install_opengrep() {
return
;;
esac
local tag url
tag="$(curl -fsSL https://api.github.com/repos/opengrep/opengrep/releases/latest | grep -m1 '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/')"
local release tag url
# Buffer the response: piping curl into an early-exiting `grep -m1` makes curl die with (23), which pipefail turns fatal.
release="$(curl -fsSL https://api.github.com/repos/opengrep/opengrep/releases/latest)"
tag="$(grep -m1 '"tag_name"' <<<"$release" | sed -E 's/.*"([^"]+)".*/\1/')"
if [[ -z "$tag" ]]; then
warn "opengrep: could not resolve latest release tag, install manually"
return
@@ -54,8 +57,6 @@ install_opengrep() {
chmod +x "$SKILL_DIR/.venv/bin/opengrep"
}
install_opengrep
install_powershell_modules() {
if ! command -v pwsh >/dev/null 2>&1; then
@@ -67,16 +68,14 @@ install_powershell_modules() {
pwsh -NoProfile -NonInteractive -Command '
foreach ($m in "PSScriptAnalyzer", "InjectionHunter") {
if (Get-Module -ListAvailable -Name $m) {
Write-Host " $m: already installed"
Write-Host " ${m}: already installed"
} else {
Install-Module -Name $m -Scope CurrentUser -Force -AcceptLicense -Repository PSGallery
Write-Host " $m: installed"
Write-Host " ${m}: installed"
}
}'
}
install_powershell_modules
install_native_brew() {
say "Installing native tools via Homebrew"
@@ -113,7 +112,15 @@ install_native_arch() {
fi
say "Installing native tools via $helper"
local pkgs=(gitleaks github-cli osv-scanner)
# --needed still invokes sudo, so skip the helper entirely when nothing is missing.
local pkgs=() pkg
for pkg in gitleaks github-cli osv-scanner; do
pacman -Q "$pkg" >/dev/null 2>&1 || pkgs+=("$pkg")
done
if [[ ${#pkgs[@]} -eq 0 ]]; then
echo " gitleaks, github-cli, osv-scanner: already installed"
return
fi
if [[ "$helper" == "pacman" ]]; then
sudo pacman -S --needed --noconfirm gitleaks github-cli || true
if ! command -v osv-scanner >/dev/null 2>&1; then
@@ -125,29 +132,66 @@ install_native_arch() {
fi
}
if command -v brew >/dev/null 2>&1; then
install_native_brew
elif command -v pacman >/dev/null 2>&1; then
install_native_arch
elif command -v apt-get >/dev/null 2>&1; then
install_native_apt
else
warn "No supported native package manager found (brew/pacman/apt). Install gitleaks, osv-scanner, gh manually."
fi
say "Verifying tool availability"
for tool in bandit ruff mypy pip-audit opengrep vulture radon interrogate lizard gitleaks osv-scanner gh pwsh; do
if [[ -x "$SKILL_DIR/.venv/bin/$tool" ]]; then
printf ' %-15s %s\n' "$tool" "(.venv/bin)"
elif command -v "$tool" >/dev/null 2>&1; then
printf ' %-15s %s\n' "$tool" "$(command -v "$tool")"
install_native() {
if command -v brew >/dev/null 2>&1; then
install_native_brew
elif command -v pacman >/dev/null 2>&1; then
install_native_arch
elif command -v apt-get >/dev/null 2>&1; then
install_native_apt
else
printf ' %-15s \033[31mmissing\033[0m\n' "$tool"
warn "No supported native package manager found (brew/pacman/apt). Install gitleaks, osv-scanner, gh manually."
fi
done
}
cat <<EOF
# Checked in .venv/bin only: each plugin version gets its own venv, and a copy on PATH says nothing about this one.
VENV_TOOLS=(bandit ruff mypy pip-audit vulture radon interrogate lizard opengrep)
NATIVE_TOOLS=(gitleaks osv-scanner gh)
verify_tools() {
say "Verifying tool availability"
local tool missing=()
for tool in "${VENV_TOOLS[@]}"; do
if [[ -x "$SKILL_DIR/.venv/bin/$tool" ]]; then
printf ' %-15s %s\n' "$tool" "(.venv/bin)"
else
missing+=("$tool")
fi
done
for tool in "${NATIVE_TOOLS[@]}" pwsh; do
if command -v "$tool" >/dev/null 2>&1; then
printf ' %-15s %s\n' "$tool" "$(command -v "$tool")"
elif [[ "$tool" == pwsh ]]; then
printf ' %-15s %s\n' "$tool" "missing (optional: PowerShell review only)"
else
missing+=("$tool")
fi
done
[[ ${#missing[@]} -eq 0 ]] && return
printf ' %-15s \033[31mmissing\033[0m\n' "${missing[@]}"
return 1
}
main() {
local user_only=0
case "${1:-}" in
"") ;;
--check-only) verify_tools; return ;;
--user-only) user_only=1 ;;
*) echo "usage: install-tools.sh [--check-only | --user-only]" >&2; return 2 ;;
esac
cd "$SKILL_DIR"
install_python_tools
install_opengrep
install_powershell_modules
if [[ $user_only -eq 1 ]]; then
warn "--user-only: skipped system packages (gitleaks, osv-scanner, gh). Re-run without it to install them."
else
install_native
fi
cat <<EOF
Per-project tools (not installed here — must live in the target repo):
- eslint + eslint-plugin-security (npm i -D)
@@ -158,3 +202,9 @@ Per-project tools (not installed here — must live in the target repo):
Run /audit-code to use the skill.
EOF
verify_tools
}
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
main "$@"
fi