Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion anti-slop-brain/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,33 @@ install_one() {
echo "Anti-Slop Brain installed to ${dest}"
}

# The Gemini loader edit is the only step that needs an interpreter, so resolve
# one lazily rather than at startup: --help must still work on a machine with no
# Python at all. Prefer python3. macOS has not shipped a bare `python` since
# Monterey removed the Python 2 stub, so hardcoding it fails there with 127.
resolve_python() {
if [ -n "${PYTHON:-}" ]; then
return
fi
local candidate
for candidate in python3 python; do
if command -v "${candidate}" >/dev/null 2>&1 &&
"${candidate}" -c 'import sys; sys.exit(0 if sys.version_info[0] == 3 else 1)' >/dev/null 2>&1; then
PYTHON="${candidate}"
return
fi
done
echo "ERROR: no Python 3 interpreter on PATH. Looked for python3, then python." >&2
exit 1
}

install_gemini() {
local dest="${base_home}/.gemini/anti-slop-brain"
local loader="${base_home}/.gemini/GEMINI.md"
install_one "${dest}"
mkdir -p "$(dirname "${loader}")"
python - "${loader}" "@./anti-slop-brain/GEMINI.md" <<'PY'
resolve_python
"${PYTHON}" - "${loader}" "@./anti-slop-brain/GEMINI.md" <<'PY'
from __future__ import annotations

import re
Expand Down
44 changes: 44 additions & 0 deletions anti-slop-brain/tests/test_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import hashlib
import json
import os
import shutil
import subprocess
import sys
import tempfile
Expand Down Expand Up @@ -95,6 +96,48 @@ def test_demo_build_is_reproducible() -> None:
)


def test_installers_run_without_a_bare_python() -> None:
"""The installers must not assume a `python` on PATH.

Regression: `install.sh` and `uninstall.sh` invoked bare `python` for the
Gemini loader edit. macOS has shipped no `python` since Monterey removed
the Python 2 stub, so `./install.sh --target all` exited 127 after it had
already copied five skill trees, leaving a half-installed surface and no
loader. `uninstall.sh` failed at the same point and stranded the loader
block inside `GEMINI.md`, which the next install then had to repair.

The ambient-PATH run in `main` cannot catch this. `actions/setup-python`
puts a `python` on PATH, so CI is green, and so is any developer machine
with a virtualenv active. This test builds a sanitized PATH carrying
`python3` and the utilities the scripts shell out to, and no `python` under
any name, which is the arrangement that actually failed.
"""
with tempfile.TemporaryDirectory(prefix="anti-slop-brain-nopython-") as tmp:
root = Path(tmp)
bin_dir = root / "bin"
bin_dir.mkdir()
for tool in ("bash", "cp", "mkdir", "rm", "chmod", "dirname", "find"):
resolved = shutil.which(tool)
assert resolved, f"cannot build a sanitized PATH without {tool}"
(bin_dir / tool).symlink_to(resolved)
# The interpreter is present under its versioned name only.
(bin_dir / "python3").symlink_to(PY)
assert shutil.which("python", path=str(bin_dir)) is None, "sanitized PATH still exposes a bare python"

env = {"PATH": str(bin_dir), "ANTI_SLOP_BRAIN_INSTALL_HOME": str(root / "home")}
loader = root / "home" / ".gemini" / "GEMINI.md"
# subprocess resolves argv[0] against the parent PATH rather than the
# env passed here, so bash is named absolutely. Everything the script
# itself looks up goes through the sanitized PATH.
bash = str(bin_dir / "bash")

run_cmd([bash, "install.sh", "--target", "gemini"], env=env)
assert "anti-slop-brain-install:start" in loader.read_text(encoding="utf-8"), "install wrote no loader block"

run_cmd([bash, "uninstall.sh", "--target", "gemini"], env=env)
assert not loader.exists(), "uninstall left the loader block behind"


def main() -> int:
run(["-m", "compileall", "scripts", "anti_slop_brain", "tests"])
run(["scripts/lint_vault.py", "--vault", "assets/template-brain", "--template"])
Expand Down Expand Up @@ -145,6 +188,7 @@ def main() -> int:
assert not (Path(tmp) / ".gemini" / "GEMINI.md").exists()
run_cmd(["bash", "uninstall.sh", "--target", "custom", "--path", str(custom_root)], env=env)
assert not (custom_root / "anti-slop-brain").exists()
test_installers_run_without_a_bare_python()
print("Pipeline tests passed")
return 0

Expand Down
24 changes: 23 additions & 1 deletion anti-slop-brain/uninstall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,34 @@ remove_one() {
fi
}

# The Gemini loader cleanup is the only step that needs an interpreter, so
# resolve one lazily rather than at startup: --help must still work on a machine
# with no Python at all. Prefer python3. macOS has not shipped a bare `python`
# since Monterey removed the Python 2 stub, so hardcoding it fails with 127,
# which would strand the loader block in GEMINI.md after an uninstall.
resolve_python() {
if [ -n "${PYTHON:-}" ]; then
return
fi
local candidate
for candidate in python3 python; do
if command -v "${candidate}" >/dev/null 2>&1 &&
"${candidate}" -c 'import sys; sys.exit(0 if sys.version_info[0] == 3 else 1)' >/dev/null 2>&1; then
PYTHON="${candidate}"
return
fi
done
echo "ERROR: no Python 3 interpreter on PATH. Looked for python3, then python." >&2
exit 1
}

remove_gemini() {
local dir="${base_home}/.gemini/anti-slop-brain"
local loader="${base_home}/.gemini/GEMINI.md"
remove_one "${dir}"
if [ -f "${loader}" ]; then
python - "${loader}" <<'PY'
resolve_python
"${PYTHON}" - "${loader}" <<'PY'
from __future__ import annotations

import re
Expand Down