mirror of
https://github.com/safishamsi/graphify.git
synced 2026-07-26 09:11:30 +00:00
Fix PreToolUse hook for Claude Code v2.1.117+ (Glob|Grep → Bash matcher)
Grep and Glob tools removed in CC v2.1.117; searches now go through Bash. Hook now reads stdin tool_input and pattern-matches on search commands. Uninstall/reinstall handles both old and new matcher for clean upgrades. Closes #578 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
a566bfb566
commit
ee1df22b25
@@ -51,6 +51,10 @@ dist/
|
|||||||
|
|
||||||
Same syntax as `.gitignore`. You can keep a single `.graphifyignore` at your repo root — patterns work correctly even when graphify is run on a subfolder.
|
Same syntax as `.gitignore`. You can keep a single `.graphifyignore` at your repo root — patterns work correctly even when graphify is run on a subfolder.
|
||||||
|
|
||||||
|
## What's new in v0.5.2
|
||||||
|
|
||||||
|
- **Hook fix for Claude Code v2.1.117+** — the PreToolUse hook now matches on `Bash` instead of `Glob|Grep`. Claude Code v2.1.117 removed dedicated Grep/Glob tools; searches now go through Bash. The hook inspects the command string and only fires on search-like calls (grep, rg, find, fd etc.), so it does not trigger on every shell command.
|
||||||
|
|
||||||
## What's new in v0.5.1
|
## What's new in v0.5.1
|
||||||
|
|
||||||
- **Node ID collision fix** — files sharing the same name in different directories (e.g. two `utils.py` files) now get unique IDs by prefixing the parent directory name.
|
- **Node ID collision fix** — files sharing the same name in different directories (e.g. two `utils.py` files) now get unique IDs by prefixing the parent directory name.
|
||||||
|
|||||||
+14
-6
@@ -37,14 +37,22 @@ def _refresh_all_version_stamps() -> None:
|
|||||||
vf.write_text(__version__, encoding="utf-8")
|
vf.write_text(__version__, encoding="utf-8")
|
||||||
|
|
||||||
_SETTINGS_HOOK = {
|
_SETTINGS_HOOK = {
|
||||||
"matcher": "Glob|Grep",
|
# Claude Code v2.1.117+ removed dedicated Grep/Glob tools; searches now go through Bash.
|
||||||
|
# We match on Bash and inspect the command string to avoid firing on every shell call.
|
||||||
|
"matcher": "Bash",
|
||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": (
|
"command": (
|
||||||
"[ -f graphify-out/graph.json ] && "
|
"CMD=$(python3 -c \""
|
||||||
r"""echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":"graphify: Knowledge graph exists. Read graphify-out/GRAPH_REPORT.md for god nodes and community structure before searching raw files."}}' """
|
"import json,sys; d=json.load(sys.stdin); "
|
||||||
"|| true"
|
"print(d.get('tool_input',d).get('command',''))\" 2>/dev/null || true); "
|
||||||
|
"case \"$CMD\" in "
|
||||||
|
"*grep*|*rg\ *|*ripgrep*|*find\ *|*fd\ *|*ack\ *|*ag\ *) "
|
||||||
|
" [ -f graphify-out/graph.json ] && "
|
||||||
|
r""" echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":"graphify: Knowledge graph exists. Read graphify-out/GRAPH_REPORT.md for god nodes and community structure before searching raw files."}}' """
|
||||||
|
" || true ;; "
|
||||||
|
"esac"
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -857,7 +865,7 @@ def _install_claude_hook(project_dir: Path) -> None:
|
|||||||
hooks = settings.setdefault("hooks", {})
|
hooks = settings.setdefault("hooks", {})
|
||||||
pre_tool = hooks.setdefault("PreToolUse", [])
|
pre_tool = hooks.setdefault("PreToolUse", [])
|
||||||
|
|
||||||
hooks["PreToolUse"] = [h for h in pre_tool if not (h.get("matcher") == "Glob|Grep" and "graphify" in str(h))]
|
hooks["PreToolUse"] = [h for h in pre_tool if not (h.get("matcher") in ("Glob|Grep", "Bash") and "graphify" in str(h))]
|
||||||
hooks["PreToolUse"].append(_SETTINGS_HOOK)
|
hooks["PreToolUse"].append(_SETTINGS_HOOK)
|
||||||
settings_path.write_text(json.dumps(settings, indent=2), encoding="utf-8")
|
settings_path.write_text(json.dumps(settings, indent=2), encoding="utf-8")
|
||||||
print(f" .claude/settings.json -> PreToolUse hook registered")
|
print(f" .claude/settings.json -> PreToolUse hook registered")
|
||||||
@@ -873,7 +881,7 @@ def _uninstall_claude_hook(project_dir: Path) -> None:
|
|||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
return
|
return
|
||||||
pre_tool = settings.get("hooks", {}).get("PreToolUse", [])
|
pre_tool = settings.get("hooks", {}).get("PreToolUse", [])
|
||||||
filtered = [h for h in pre_tool if not (h.get("matcher") == "Glob|Grep" and "graphify" in str(h))]
|
filtered = [h for h in pre_tool if not (h.get("matcher") in ("Glob|Grep", "Bash") and "graphify" in str(h))]
|
||||||
if len(filtered) == len(pre_tool):
|
if len(filtered) == len(pre_tool):
|
||||||
return
|
return
|
||||||
settings["hooks"]["PreToolUse"] = filtered
|
settings["hooks"]["PreToolUse"] = filtered
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "graphifyy"
|
name = "graphifyy"
|
||||||
version = "0.5.1"
|
version = "0.5.2"
|
||||||
description = "AI coding assistant skill (Claude Code, Codex, OpenCode, Cursor, Gemini CLI, Aider, OpenClaw, Factory Droid, Trae, Hermes, Kiro, Google Antigravity) - turn any folder of code, docs, papers, images, or videos into a queryable knowledge graph"
|
description = "AI coding assistant skill (Claude Code, Codex, OpenCode, Cursor, Gemini CLI, Aider, OpenClaw, Factory Droid, Trae, Hermes, Kiro, Google Antigravity) - turn any folder of code, docs, papers, images, or videos into a queryable knowledge graph"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = { file = "LICENSE" }
|
license = { file = "LICENSE" }
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ def test_install_creates_settings_json(tmp_path):
|
|||||||
assert settings_path.exists()
|
assert settings_path.exists()
|
||||||
settings = json.loads(settings_path.read_text())
|
settings = json.loads(settings_path.read_text())
|
||||||
hooks = settings.get("hooks", {}).get("PreToolUse", [])
|
hooks = settings.get("hooks", {}).get("PreToolUse", [])
|
||||||
assert any("Glob|Grep" in h.get("matcher", "") for h in hooks)
|
assert any(h.get("matcher") == "Bash" for h in hooks)
|
||||||
|
|
||||||
|
|
||||||
def test_install_settings_json_idempotent(tmp_path):
|
def test_install_settings_json_idempotent(tmp_path):
|
||||||
@@ -120,8 +120,8 @@ def test_install_settings_json_idempotent(tmp_path):
|
|||||||
settings_path = tmp_path / ".claude" / "settings.json"
|
settings_path = tmp_path / ".claude" / "settings.json"
|
||||||
settings = json.loads(settings_path.read_text())
|
settings = json.loads(settings_path.read_text())
|
||||||
hooks = settings.get("hooks", {}).get("PreToolUse", [])
|
hooks = settings.get("hooks", {}).get("PreToolUse", [])
|
||||||
glob_grep_hooks = [h for h in hooks if "Glob|Grep" in h.get("matcher", "")]
|
bash_hooks = [h for h in hooks if h.get("matcher") == "Bash" and "graphify" in str(h)]
|
||||||
assert len(glob_grep_hooks) == 1
|
assert len(bash_hooks) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_uninstall_removes_settings_hook(tmp_path):
|
def test_uninstall_removes_settings_hook(tmp_path):
|
||||||
@@ -133,4 +133,4 @@ def test_uninstall_removes_settings_hook(tmp_path):
|
|||||||
if settings_path.exists():
|
if settings_path.exists():
|
||||||
settings = json.loads(settings_path.read_text())
|
settings = json.loads(settings_path.read_text())
|
||||||
hooks = settings.get("hooks", {}).get("PreToolUse", [])
|
hooks = settings.get("hooks", {}).get("PreToolUse", [])
|
||||||
assert not any("Glob|Grep" in h.get("matcher", "") for h in hooks)
|
assert not any(h.get("matcher") == "Bash" and "graphify" in str(h) for h in hooks)
|
||||||
|
|||||||
Reference in New Issue
Block a user