* Handle codegraph_search
(args: Record<string, unknown>)
| 2157 | * Handle codegraph_search |
| 2158 | */ |
| 2159 | private async handleSearch(args: Record<string, unknown>): Promise<ToolResult> { |
| 2160 | const query = this.validateString(args.query, 'query'); |
| 2161 | if (typeof query !== 'string') return query; |
| 2162 | |
| 2163 | const cg = this.getCodeGraph(args.projectPath as string | undefined); |
| 2164 | const rawKind = args.kind as string | undefined; |
| 2165 | // The schema enum says 'type' (what agents naturally reach for); the |
| 2166 | // NodeKind is 'type_alias'. Without the mapping, kind: "type" silently |
| 2167 | // matched nothing — a filter value we advertise must work. |
| 2168 | const kind = rawKind === 'type' ? 'type_alias' : rawKind; |
| 2169 | const rawLimit = Number(args.limit) || 10; |
| 2170 | const limit = clamp(rawLimit, 1, 100); |
| 2171 | |
| 2172 | const results = cg.searchNodes(query, { |
| 2173 | limit, |
| 2174 | kinds: kind ? [kind as NodeKind] : undefined, |
| 2175 | }); |
| 2176 | |
| 2177 | if (results.length === 0) { |
| 2178 | return this.textResult(`No results found for "${query}"`); |
| 2179 | } |
| 2180 | |
| 2181 | // Down-rank generated files within the FTS-returned set so a search |
| 2182 | // for "Send" surfaces the hand-written keeper before .pb.go stubs |
| 2183 | // that share the name. Stable: only reorders generated vs. not. |
| 2184 | const isGen = cg.generatedFilePredicate(results.map((r) => r.node.filePath)); |
| 2185 | const ranked = [...results].sort((a, b) => { |
| 2186 | const aGen = isGen(a.node.filePath) ? 1 : 0; |
| 2187 | const bGen = isGen(b.node.filePath) ? 1 : 0; |
| 2188 | return aGen - bGen; |
| 2189 | }); |
| 2190 | |
| 2191 | const formatted = this.formatSearchResults(ranked); |
| 2192 | return this.textResult(this.truncateOutput(formatted)); |
| 2193 | } |
| 2194 | |
| 2195 | /** |
| 2196 | * Group symbol matches into DISTINCT DEFINITIONS — one group per |
no test coverage detected