1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
//! `mushroomdb intercept` — the optional `PreToolUse` hook body (experimental).
//!
//! Claude Code runs this before a `Grep`, handing it the tool call as JSON on
//! stdin. When the pattern is a bare identifier the graph already holds as a
//! symbol, the search is a question `explore` answers exactly — the definition,
//! the callers and the callees, in one reply — where the grep returns every
//! line the name appears on and leaves the reading to the model. Exit 2 with a
//! message on stderr is Claude Code's way of saying so: the tool call is
//! blocked and the message reaches the model.
//!
//! # Why it is this conservative
//!
//! A hook that blocks a search wrongly is far worse than one that never fires,
//! so [`decide`] answers `Some` only where the graph provably has the better
//! answer:
//!
//! - The pattern must be a bare identifier ([`is_identifier`]). Anything with
//! regex syntax in it — `.`, `*`, `|`, an anchor, a space — is a search, not
//! a name, and the graph has no opinion about it.
//! - It must be at least three characters. `id` may well be a symbol, and a
//! grep for it is still almost certainly not a request for that symbol.
//! - It must resolve to at least one `Symbol` node, through the same lookup
//! `context` uses for a bare name, so the `explore` the message points at is
//! one that will actually answer.
//!
//! Everything else — a store that will not open, a payload that will not parse,
//! a missing field — is silence and exit 0, like every other hook this binary
//! writes.
use crateopen_for_hook;
use crateDb;
use repograph;
use Path;
/// Shortest pattern worth redirecting. Two-character names are common enough
/// as substrings that a grep for one is not evidence the symbol was meant.
const MIN_PATTERN_LEN: usize = 3;
/// Whether `pattern` is a bare identifier: `^[A-Za-z_][A-Za-z0-9_:]{2,}$`.
///
/// The `:` is there for the qualified names extractors produce (`Type::method`)
/// — still one name, still something the graph can be asked about — and it is
/// not a regex metacharacter, so a pattern containing it is no more likely to
/// be a search than a plain name is.
///
/// Shared with [`crate::enrich`], which asks the same question of the tokens in
/// a search result: the floor that makes a pattern worth a lookup is the floor
/// that makes a matched word worth one.
pub
/// The payload's search pattern, when it is a name the graph could hold.
/// `None` for a missing or non-string `tool_input.pattern` and for anything
/// that is not a bare identifier — neither needs the store to be opened.
/// Whether this `Grep` should be redirected, and what to say if so.
///
/// `input` is Claude Code's `PreToolUse` payload; the pattern is
/// `tool_input.pattern`. `None` means "no opinion — run the grep".
/// The whole hook body: parse the payload, open the store, decide.
///
/// `None` for every failure as well as for every pattern that passes through,
/// because the caller's only two options are "block with this message" and
/// "stay out of the way", and a store that is missing or busy is the second.
///
/// The pattern is tested before the store is opened. Opening one is the whole
/// cost of this hook — measured at ~360 ms on a small store, against ~0 for
/// the parse — and this runs before every single `Grep`, most of which are
/// searches no identifier test will ever accept.