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
129
130
131
132
133
134
135
136
137
138
139
140
//! `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 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.
/// 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.