ee1df22 narrowed the Claude Code search-guard matcher from "Glob|Grep" to
"Bash" on the premise that dedicated search tools were removed and searches
go through Bash. Current Claude Code routes content search through its
first-class Grep tool (its Bash tool description actively steers away from
shell grep), so the graphify-first nudge never fired on the agent's primary
exploration path and the graph was silently bypassed.
Three-part fix, per the issue's analysis:
- Matcher: "Bash" -> "Bash|Grep" in _claude_pretooluse_hooks. Glob already
fires the read nudge via "Read|Glob", so Grep was the only orphaned tool.
- Guard body: the hook-guard search branch only inspected tool_input.command,
which a Grep call doesn't carry (it has pattern/path/glob). A Grep-shaped
input (pattern present, no command) is now treated as a search — it IS one
by definition — and nudges whenever a fresh graph exists. The Bash
token-matching path is unchanged, and a command-carrying input never
triggers the Grep shape, so non-search Bash calls stay silent.
- Idempotency: "Bash|Grep" added to the four install/uninstall dedup filters
(claude + codebuddy), so upgrading replaces the stale "Bash" hook in place
instead of appending a duplicate — verified against a pre-fix settings.json.
Tests: new regression tests feed Grep-shaped tool_input through
hook-guard search and assert the nudge (with graph), silence (without),
valid PreToolUse JSON, and no blocking; plus a guard that a non-search Bash
command with a stray pattern key does not nudge. Existing matcher assertions
updated across test_search_hook/test_install/test_claude_md/test_codebuddy/
test_hook_strict. Hook+install suites: 397 passed. Full suite: 3224 passed;
the 13 failures are pre-existing on clean v8 in this environment.
Fixes#1986
`graphify uninstall` (and `graphify claude uninstall`) only looked at
`.claude/settings.json` and root `CLAUDE.md`. A user who relocates the
PreToolUse hook into `.claude/settings.local.json` and the `## graphify`
instructions into `CLAUDE.local.md` / `.claude/CLAUDE.local.md` - so they
are not committed to a shared repo - was left with both behind after
uninstall, which reported "nothing to do".
- `_uninstall_claude_hook` now strips the hook from both `settings.json`
and `settings.local.json` (factored into `_strip_graphify_hook`).
- `claude_uninstall` now strips the `## graphify` section from `CLAUDE.md`,
root `CLAUDE.local.md`, and `.claude/CLAUDE.local.md` (factored into
`_strip_graphify_md_section`), cleaning every present file rather than the
first, and always runs the hook cleanup.
- `_strip_graphify_md_section` reads the two newly-covered files defensively
so a non-UTF-8 or otherwise unreadable file can't abort uninstall.
Unrelated local-only files (no graphify content) are left byte-for-byte
untouched. The `--local` install flag suggested in the issue is left out of
scope; this change makes uninstall symmetric with wherever the config lives.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
- #352: add skill-kiro.md to pyproject.toml package-data
- #341: guard edge_betweenness at >5000 nodes; use approximate k=100 for suggest_questions on large graphs
- #354/#229: add Step 6b in skill.md to call to_wiki() when --wiki given (before Step 9 cleanup)
- #356: call _install_opencode_plugin() from install --platform opencode path
- #350: add cache_root param to extract() so subdirectory runs keep cache at ./graphify-out/cache/
- #230: PHP class_constant_access_expression emits references_constant edges
- #232: PHP scoped_call_expression (static method calls) emits calls edges
- #287: os.replace fallback for Windows WinError 5; graphify update exits 1 on failure; templates use graphify update . instead of python3 -c
- #348: cross-file call resolution for all languages via raw_calls + global label map pass in extract()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>