Files
graphify/tests/test_gemini_hook.py
T
safishamsi 85f9fac7f7 Fix: port the Gemini CLI BeforeTool graph-nudge hook to be shell-agnostic too (#522)
The Gemini hook was a `python -c "..."` one-liner that (a) depended on a bare
`python` being on PATH (frequently `python`/`py` or absent on Windows) and
(b) embedded backticks + escaped quotes that Windows PowerShell mangles. Same
fix as the Claude/Codebuddy hooks: it now invokes `graphify hook-guard gemini`
via the absolute exe path.

The gemini mode always returns {"decision":"allow"} (never blocks a tool) and
appends the graph nudge as additionalContext only when graph.json exists — the
BeforeTool contract Gemini expects, byte-identical to the old payload. It takes
no stdin, honors GRAPHIFY_OUT, and the matcher stays "read_file|list_directory"
so install/uninstall find and replace old hooks unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 16:43:21 +01:00

72 lines
2.3 KiB
Python

"""The Gemini CLI BeforeTool guard nudges toward the graph, shell-agnostically.
Since #522 it runs as `graphify hook-guard gemini` (not a `python -c` one-liner
that depended on a bare `python` on PATH and embedded PowerShell-hostile
backticks). It always returns {"decision":"allow"} so a tool is never blocked,
and appends additionalContext only when a graph exists.
"""
import json
import os
import subprocess
import sys
from graphify.__main__ import _gemini_hook
def _env():
e = dict(os.environ)
e.pop("GRAPHIFY_OUT", None)
return e
def _run(cwd, *, graph: bool):
if graph:
(cwd / "graphify-out").mkdir(parents=True, exist_ok=True)
(cwd / "graphify-out" / "graph.json").write_text("{}", encoding="utf-8")
return subprocess.run(
[sys.executable, "-m", "graphify", "hook-guard", "gemini"],
input="", capture_output=True, text=True, cwd=cwd, env=_env(),
)
def test_matcher_and_command_shape():
h = _gemini_hook()
assert h["matcher"] == "read_file|list_directory"
cmd = h["hooks"][0]["command"]
# #522: no bare `python` dependency, no embedded quote/backtick soup.
assert "python -c" not in cmd
assert "graphify" in cmd and "hook-guard gemini" in cmd
def test_allows_and_nudges_with_graph(tmp_path):
out = _run(tmp_path, graph=True).stdout
payload = json.loads(out)
assert payload["decision"] == "allow"
assert "graphify query" in payload["additionalContext"]
def test_allows_without_nudge_when_no_graph(tmp_path):
out = _run(tmp_path, graph=False).stdout
payload = json.loads(out)
assert payload["decision"] == "allow"
assert "additionalContext" not in payload
def test_never_blocks(tmp_path):
r = _run(tmp_path, graph=True)
assert r.returncode == 0
payload = json.loads(r.stdout)
assert payload["decision"] == "allow"
def test_honors_graphify_out_override(tmp_path):
custom = tmp_path / "custom-out"
custom.mkdir()
(custom / "graph.json").write_text("{}", encoding="utf-8")
env = dict(os.environ, GRAPHIFY_OUT=str(custom))
r = subprocess.run(
[sys.executable, "-m", "graphify", "hook-guard", "gemini"],
input="", capture_output=True, text=True, cwd=tmp_path, env=env,
)
assert "graphify query" in json.loads(r.stdout).get("additionalContext", "")