grepmax 0.18.1 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +23 -0
- package/README.md +28 -3
- package/dist/commands/add.js +4 -2
- package/dist/commands/claude-code.js +1 -1
- package/dist/commands/codex.js +3 -1
- package/dist/commands/context.js +1 -1
- package/dist/commands/dead.js +2 -6
- package/dist/commands/doctor.js +26 -7
- package/dist/commands/extract.js +3 -5
- package/dist/commands/impact.js +6 -4
- package/dist/commands/index.js +7 -2
- package/dist/commands/log.js +4 -2
- package/dist/commands/mcp.js +594 -722
- package/dist/commands/peek.js +15 -9
- package/dist/commands/plugin.js +36 -22
- package/dist/commands/project.js +16 -6
- package/dist/commands/related.js +8 -9
- package/dist/commands/remove.js +9 -2
- package/dist/commands/review.js +1 -1
- package/dist/commands/setup.js +4 -2
- package/dist/commands/similar.js +4 -4
- package/dist/commands/status.js +6 -4
- package/dist/commands/summarize.js +6 -4
- package/dist/commands/symbols.js +1 -1
- package/dist/commands/test-find.js +2 -2
- package/dist/commands/trace.js +12 -5
- package/dist/commands/watch.js +33 -9
- package/dist/eval-graph-nav.js +4 -1
- package/dist/eval-graph-recovery-probe.js +56 -14
- package/dist/eval-graph-spotcheck.js +10 -2
- package/dist/eval-graph-totals.js +5 -2
- package/dist/eval-oss.js +212 -37
- package/dist/eval-seed.js +13 -4
- package/dist/index.js +9 -9
- package/dist/lib/daemon/daemon.js +32 -13
- package/dist/lib/daemon/ipc-handler.js +24 -5
- package/dist/lib/daemon/mlx-server-manager.js +11 -3
- package/dist/lib/daemon/process-manager.js +1 -2
- package/dist/lib/daemon/watcher-manager.js +22 -6
- package/dist/lib/graph/callsites.js +151 -25
- package/dist/lib/graph/graph-builder.js +2 -2
- package/dist/lib/graph/impact.js +6 -6
- package/dist/lib/index/batch-processor.js +16 -6
- package/dist/lib/index/chunker.js +2 -0
- package/dist/lib/index/syncer.js +22 -10
- package/dist/lib/index/watcher-batch.js +10 -1
- package/dist/lib/llm/config.js +2 -1
- package/dist/lib/llm/diff.js +56 -11
- package/dist/lib/llm/investigate.js +52 -11
- package/dist/lib/llm/review.js +21 -8
- package/dist/lib/llm/server.js +25 -9
- package/dist/lib/llm/tools.js +1 -1
- package/dist/lib/output/agent-search-formatter.js +25 -3
- package/dist/lib/output/index-state-footer.js +1 -1
- package/dist/lib/review/risk.js +2 -4
- package/dist/lib/search/pagerank.js +1 -1
- package/dist/lib/search/searcher.js +43 -17
- package/dist/lib/search/seed-weight.js +4 -1
- package/dist/lib/skeleton/symbol-extractor.js +2 -1
- package/dist/lib/store/vector-db.js +21 -10
- package/dist/lib/utils/cross-project.js +5 -1
- package/dist/lib/utils/daemon-client.js +39 -1
- package/dist/lib/utils/filter-builder.js +22 -0
- package/dist/lib/utils/git.js +10 -2
- package/dist/lib/utils/project-registry.js +3 -2
- package/dist/lib/utils/scope-filter.js +6 -6
- package/dist/lib/utils/watcher-launcher.js +4 -1
- package/dist/lib/utils/watcher-store.js +2 -1
- package/dist/lib/workers/embeddings/granite.js +4 -1
- package/dist/lib/workers/embeddings/mlx-client.js +7 -2
- package/dist/lib/workers/pool.js +30 -7
- package/dist/lib/workers/process-child.js +1 -1
- package/package.json +23 -19
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/plugins/grepmax/hooks/cwd-changed.js +4 -1
- package/plugins/grepmax/hooks/pre-grep.js +2 -1
- package/plugins/grepmax/hooks/start.js +45 -10
- package/plugins/grepmax/hooks/subagent-start.js +1 -4
package/dist/commands/mcp.js
CHANGED
|
@@ -53,16 +53,18 @@ exports.mcp = void 0;
|
|
|
53
53
|
exports.toStringArray = toStringArray;
|
|
54
54
|
exports.ok = ok;
|
|
55
55
|
exports.err = err;
|
|
56
|
+
exports.searchResultPath = searchResultPath;
|
|
57
|
+
exports.searchResultStartLine = searchResultStartLine;
|
|
58
|
+
exports.searchResultEndLine = searchResultEndLine;
|
|
56
59
|
exports.filterMcpSearchResults = filterMcpSearchResults;
|
|
57
60
|
exports.formatMcpPointerSearchResults = formatMcpPointerSearchResults;
|
|
58
61
|
const fs = __importStar(require("node:fs"));
|
|
59
62
|
const path = __importStar(require("node:path"));
|
|
60
|
-
const
|
|
63
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
61
64
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
62
|
-
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
63
65
|
const commander_1 = require("commander");
|
|
66
|
+
const zod_1 = require("zod");
|
|
64
67
|
const config_1 = require("../config");
|
|
65
|
-
const audit_1 = require("./audit");
|
|
66
68
|
const graph_builder_1 = require("../lib/graph/graph-builder");
|
|
67
69
|
const index_config_1 = require("../lib/index/index-config");
|
|
68
70
|
const syncer_1 = require("../lib/index/syncer");
|
|
@@ -82,482 +84,7 @@ const project_registry_1 = require("../lib/utils/project-registry");
|
|
|
82
84
|
const project_root_1 = require("../lib/utils/project-root");
|
|
83
85
|
const watcher_launcher_1 = require("../lib/utils/watcher-launcher");
|
|
84
86
|
const watcher_store_1 = require("../lib/utils/watcher-store");
|
|
85
|
-
|
|
86
|
-
// Tool definitions
|
|
87
|
-
// ---------------------------------------------------------------------------
|
|
88
|
-
const TOOLS = [
|
|
89
|
-
{
|
|
90
|
-
name: "semantic_search",
|
|
91
|
-
description: "Search code by meaning. Use scope:'all' for cross-project. Prefer CLI: gmax \"query\" --plain",
|
|
92
|
-
inputSchema: {
|
|
93
|
-
type: "object",
|
|
94
|
-
properties: {
|
|
95
|
-
query: {
|
|
96
|
-
type: "string",
|
|
97
|
-
description: "Natural language query (5+ words recommended)",
|
|
98
|
-
},
|
|
99
|
-
limit: {
|
|
100
|
-
type: "number",
|
|
101
|
-
description: "Max results (default 3, max 50)",
|
|
102
|
-
},
|
|
103
|
-
root: {
|
|
104
|
-
type: "string",
|
|
105
|
-
description: "Search a different directory (absolute path)",
|
|
106
|
-
},
|
|
107
|
-
path: {
|
|
108
|
-
type: "string",
|
|
109
|
-
description: "Path prefix filter (e.g. 'src/auth/')",
|
|
110
|
-
},
|
|
111
|
-
detail: {
|
|
112
|
-
type: "string",
|
|
113
|
-
description: "'pointer' (default), 'code', or 'full'",
|
|
114
|
-
},
|
|
115
|
-
min_score: { type: "number", description: "Min score 0-1 (default 0)" },
|
|
116
|
-
max_per_file: { type: "number", description: "Max results per file" },
|
|
117
|
-
file: {
|
|
118
|
-
type: "string",
|
|
119
|
-
description: "Filename filter (e.g. 'syncer.ts')",
|
|
120
|
-
},
|
|
121
|
-
exclude: {
|
|
122
|
-
type: "string",
|
|
123
|
-
description: "Exclude path prefix (e.g. 'tests/')",
|
|
124
|
-
},
|
|
125
|
-
language: {
|
|
126
|
-
type: "string",
|
|
127
|
-
description: "Extension filter (e.g. 'ts', 'py')",
|
|
128
|
-
},
|
|
129
|
-
role: {
|
|
130
|
-
type: "string",
|
|
131
|
-
description: "'ORCHESTRATION', 'DEFINITION', or 'IMPLEMENTATION'",
|
|
132
|
-
},
|
|
133
|
-
context_lines: {
|
|
134
|
-
type: "number",
|
|
135
|
-
description: "Lines before/after chunk (max 20)",
|
|
136
|
-
},
|
|
137
|
-
mode: {
|
|
138
|
-
type: "string",
|
|
139
|
-
description: "'default' or 'symbol' (appends call graph)",
|
|
140
|
-
},
|
|
141
|
-
include_imports: {
|
|
142
|
-
type: "boolean",
|
|
143
|
-
description: "Prepend file imports to results",
|
|
144
|
-
},
|
|
145
|
-
name_pattern: {
|
|
146
|
-
type: "string",
|
|
147
|
-
description: "Regex filter on symbol name",
|
|
148
|
-
},
|
|
149
|
-
scope: {
|
|
150
|
-
type: "string",
|
|
151
|
-
description: "'project' (default) or 'all' (search everything)",
|
|
152
|
-
},
|
|
153
|
-
projects: {
|
|
154
|
-
type: "string",
|
|
155
|
-
description: "Project names to include (comma-separated)",
|
|
156
|
-
},
|
|
157
|
-
exclude_projects: {
|
|
158
|
-
type: "string",
|
|
159
|
-
description: "Project names to exclude (comma-separated)",
|
|
160
|
-
},
|
|
161
|
-
seed_files: {
|
|
162
|
-
type: "string",
|
|
163
|
-
description: "Bias results toward your working context: comma-separated paths you have open (e.g. 'src/lib/llm/server.ts'). On-topic chunks in these files get lifted; off-topic ones are not.",
|
|
164
|
-
},
|
|
165
|
-
seed_symbols: {
|
|
166
|
-
type: "string",
|
|
167
|
-
description: "Bias results toward identifiers you're working with: comma-separated symbol names. Chunks defining a seeded symbol are preferred over mere callers.",
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
required: ["query"],
|
|
171
|
-
},
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
name: "code_skeleton",
|
|
175
|
-
description: "File structure with bodies collapsed (~4x fewer tokens). Accepts file, directory, or comma-separated paths.",
|
|
176
|
-
inputSchema: {
|
|
177
|
-
type: "object",
|
|
178
|
-
properties: {
|
|
179
|
-
target: {
|
|
180
|
-
type: "string",
|
|
181
|
-
description: "File, directory, or comma-separated paths",
|
|
182
|
-
},
|
|
183
|
-
limit: {
|
|
184
|
-
type: "number",
|
|
185
|
-
description: "Max files for directory mode (default 10)",
|
|
186
|
-
},
|
|
187
|
-
format: { type: "string", description: "'text' (default) or 'json'" },
|
|
188
|
-
},
|
|
189
|
-
required: ["target"],
|
|
190
|
-
},
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
name: "trace_calls",
|
|
194
|
-
description: "Call graph: importers, callers (multi-hop), callees with file:line.",
|
|
195
|
-
inputSchema: {
|
|
196
|
-
type: "object",
|
|
197
|
-
properties: {
|
|
198
|
-
symbol: { type: "string", description: "Function/class name to trace" },
|
|
199
|
-
depth: {
|
|
200
|
-
type: "number",
|
|
201
|
-
description: "Caller depth (default 1, max 3)",
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
required: ["symbol"],
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
name: "extract_symbol",
|
|
209
|
-
description: "Extract complete function/class body by symbol name.",
|
|
210
|
-
inputSchema: {
|
|
211
|
-
type: "object",
|
|
212
|
-
properties: {
|
|
213
|
-
symbol: { type: "string", description: "Symbol name to extract" },
|
|
214
|
-
root: { type: "string", description: "Project root (absolute path)" },
|
|
215
|
-
include_imports: {
|
|
216
|
-
type: "boolean",
|
|
217
|
-
description: "Prepend file imports",
|
|
218
|
-
},
|
|
219
|
-
},
|
|
220
|
-
required: ["symbol"],
|
|
221
|
-
},
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
name: "peek_symbol",
|
|
225
|
-
description: "Compact symbol overview: signature + callers + callees.",
|
|
226
|
-
inputSchema: {
|
|
227
|
-
type: "object",
|
|
228
|
-
properties: {
|
|
229
|
-
symbol: { type: "string", description: "Symbol name" },
|
|
230
|
-
root: { type: "string", description: "Project root (absolute path)" },
|
|
231
|
-
depth: {
|
|
232
|
-
type: "number",
|
|
233
|
-
description: "Caller depth (default 1, max 3)",
|
|
234
|
-
},
|
|
235
|
-
},
|
|
236
|
-
required: ["symbol"],
|
|
237
|
-
},
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
name: "dead",
|
|
241
|
-
description: "Report whether a symbol has zero inbound callers in the indexed call graph. Returns DEAD, PUBLIC EXPORT (exported with no internal callers), or LIVE with caller count. Hypothesis, not proof: dynamic dispatch, reflection, and string-built call sites won't show up.",
|
|
242
|
-
inputSchema: {
|
|
243
|
-
type: "object",
|
|
244
|
-
properties: {
|
|
245
|
-
symbol: { type: "string", description: "Symbol name to check" },
|
|
246
|
-
root: { type: "string", description: "Project root (absolute path)" },
|
|
247
|
-
},
|
|
248
|
-
required: ["symbol"],
|
|
249
|
-
},
|
|
250
|
-
},
|
|
251
|
-
{
|
|
252
|
-
name: "audit",
|
|
253
|
-
description: "Graph-summary of the indexed project in one call: god nodes (most depended-upon symbols), hub files (most depended-upon files), and dead-code candidates (non-exported symbols with zero inbound references). Built from the static call graph — dynamic dispatch, reflection, eval, and type-position-only references are invisible, so dead candidates are hypotheses; verify with the `dead` tool before acting.",
|
|
254
|
-
inputSchema: {
|
|
255
|
-
type: "object",
|
|
256
|
-
properties: {
|
|
257
|
-
root: {
|
|
258
|
-
type: "string",
|
|
259
|
-
description: "Project root (default: current)",
|
|
260
|
-
},
|
|
261
|
-
top: {
|
|
262
|
-
type: "number",
|
|
263
|
-
description: "How many of each category to return (default 10)",
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
|
-
},
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
name: "get_neighbors",
|
|
270
|
-
description: "Graph primitive: symbols reachable from a node along call edges within N hops. direction 'callees' = what it calls (outbound), 'callers' = what calls it (inbound). Each result carries its hop distance and definition location. Static call graph — same caveats as `dead`/`audit`.",
|
|
271
|
-
inputSchema: {
|
|
272
|
-
type: "object",
|
|
273
|
-
properties: {
|
|
274
|
-
symbol: { type: "string", description: "Starting symbol" },
|
|
275
|
-
direction: {
|
|
276
|
-
type: "string",
|
|
277
|
-
enum: ["callers", "callees"],
|
|
278
|
-
description: "Edge direction (default callees)",
|
|
279
|
-
},
|
|
280
|
-
max_hops: {
|
|
281
|
-
type: "number",
|
|
282
|
-
description: "Max hops (default 2, max 5)",
|
|
283
|
-
},
|
|
284
|
-
root: { type: "string", description: "Project root (absolute path)" },
|
|
285
|
-
},
|
|
286
|
-
required: ["symbol"],
|
|
287
|
-
},
|
|
288
|
-
},
|
|
289
|
-
{
|
|
290
|
-
name: "find_paths",
|
|
291
|
-
description: "Graph primitive: shortest call-graph path between two symbols, as a symbol sequence. direction 'callees' searches outward from `from` (does `from` transitively call `to`?), 'callers' searches inward. Returns 'no path' if unreachable within max_hops.",
|
|
292
|
-
inputSchema: {
|
|
293
|
-
type: "object",
|
|
294
|
-
properties: {
|
|
295
|
-
from: { type: "string", description: "Start symbol" },
|
|
296
|
-
to: { type: "string", description: "Target symbol" },
|
|
297
|
-
direction: {
|
|
298
|
-
type: "string",
|
|
299
|
-
enum: ["callers", "callees"],
|
|
300
|
-
description: "Search direction (default callees)",
|
|
301
|
-
},
|
|
302
|
-
max_hops: {
|
|
303
|
-
type: "number",
|
|
304
|
-
description: "Max hops (default 6, max 10)",
|
|
305
|
-
},
|
|
306
|
-
root: { type: "string", description: "Project root (absolute path)" },
|
|
307
|
-
},
|
|
308
|
-
required: ["from", "to"],
|
|
309
|
-
},
|
|
310
|
-
},
|
|
311
|
-
{
|
|
312
|
-
name: "subgraph_for_files",
|
|
313
|
-
description: "Graph primitive: the local dependency subgraph for a set of files — symbols they define, the call edges among those symbols, and their outbound external dependencies. Paths may be absolute or project-root-relative.",
|
|
314
|
-
inputSchema: {
|
|
315
|
-
type: "object",
|
|
316
|
-
properties: {
|
|
317
|
-
files: {
|
|
318
|
-
type: "array",
|
|
319
|
-
items: { type: "string" },
|
|
320
|
-
description: "File paths (absolute or root-relative)",
|
|
321
|
-
},
|
|
322
|
-
root: { type: "string", description: "Project root (absolute path)" },
|
|
323
|
-
},
|
|
324
|
-
required: ["files"],
|
|
325
|
-
},
|
|
326
|
-
},
|
|
327
|
-
{
|
|
328
|
-
name: "list_symbols",
|
|
329
|
-
description: "List indexed symbols with role and export status.",
|
|
330
|
-
inputSchema: {
|
|
331
|
-
type: "object",
|
|
332
|
-
properties: {
|
|
333
|
-
pattern: {
|
|
334
|
-
type: "string",
|
|
335
|
-
description: "Name filter (case-insensitive)",
|
|
336
|
-
},
|
|
337
|
-
limit: { type: "number", description: "Max results (default 20)" },
|
|
338
|
-
path: { type: "string", description: "Path prefix filter" },
|
|
339
|
-
},
|
|
340
|
-
},
|
|
341
|
-
},
|
|
342
|
-
{
|
|
343
|
-
name: "index_status",
|
|
344
|
-
description: "Index health: chunks, files, projects, watcher status.",
|
|
345
|
-
inputSchema: { type: "object", properties: {} },
|
|
346
|
-
_meta: { "anthropic/alwaysLoad": true },
|
|
347
|
-
},
|
|
348
|
-
{
|
|
349
|
-
name: "summarize_directory",
|
|
350
|
-
description: "Generate LLM summaries for indexed chunks.",
|
|
351
|
-
inputSchema: {
|
|
352
|
-
type: "object",
|
|
353
|
-
properties: {
|
|
354
|
-
path: {
|
|
355
|
-
type: "string",
|
|
356
|
-
description: "Directory to summarize (default: project root)",
|
|
357
|
-
},
|
|
358
|
-
limit: {
|
|
359
|
-
type: "number",
|
|
360
|
-
description: "Max chunks (default 200, max 5000)",
|
|
361
|
-
},
|
|
362
|
-
},
|
|
363
|
-
},
|
|
364
|
-
},
|
|
365
|
-
{
|
|
366
|
-
name: "summarize_project",
|
|
367
|
-
description: "Project overview: languages, structure, roles, key symbols, entry points.",
|
|
368
|
-
inputSchema: {
|
|
369
|
-
type: "object",
|
|
370
|
-
properties: {
|
|
371
|
-
root: {
|
|
372
|
-
type: "string",
|
|
373
|
-
description: "Project root (default: current)",
|
|
374
|
-
},
|
|
375
|
-
},
|
|
376
|
-
},
|
|
377
|
-
},
|
|
378
|
-
{
|
|
379
|
-
name: "related_files",
|
|
380
|
-
description: "Find dependencies and dependents of a file by shared symbols.",
|
|
381
|
-
inputSchema: {
|
|
382
|
-
type: "object",
|
|
383
|
-
properties: {
|
|
384
|
-
file: {
|
|
385
|
-
type: "string",
|
|
386
|
-
description: "File path relative to project root",
|
|
387
|
-
},
|
|
388
|
-
limit: {
|
|
389
|
-
type: "number",
|
|
390
|
-
description: "Max results per direction (default 10)",
|
|
391
|
-
},
|
|
392
|
-
},
|
|
393
|
-
required: ["file"],
|
|
394
|
-
},
|
|
395
|
-
},
|
|
396
|
-
{
|
|
397
|
-
name: "recent_changes",
|
|
398
|
-
description: "Recently modified indexed files with timestamps.",
|
|
399
|
-
inputSchema: {
|
|
400
|
-
type: "object",
|
|
401
|
-
properties: {
|
|
402
|
-
limit: {
|
|
403
|
-
type: "number",
|
|
404
|
-
description: "Max files to return (default 20)",
|
|
405
|
-
},
|
|
406
|
-
root: {
|
|
407
|
-
type: "string",
|
|
408
|
-
description: "Project root (defaults to current project)",
|
|
409
|
-
},
|
|
410
|
-
},
|
|
411
|
-
},
|
|
412
|
-
},
|
|
413
|
-
{
|
|
414
|
-
name: "diff_changes",
|
|
415
|
-
description: "Search code scoped to git changes. Omit ref for uncommitted changes.",
|
|
416
|
-
inputSchema: {
|
|
417
|
-
type: "object",
|
|
418
|
-
properties: {
|
|
419
|
-
ref: {
|
|
420
|
-
type: "string",
|
|
421
|
-
description: "Git ref to diff against (e.g. main, HEAD~5)",
|
|
422
|
-
},
|
|
423
|
-
query: {
|
|
424
|
-
type: "string",
|
|
425
|
-
description: "Semantic search within changed files",
|
|
426
|
-
},
|
|
427
|
-
limit: { type: "number", description: "Max results (default 10)" },
|
|
428
|
-
role: {
|
|
429
|
-
type: "string",
|
|
430
|
-
description: "Filter by role: ORCHESTRATION, DEFINITION, IMPLEMENTATION",
|
|
431
|
-
},
|
|
432
|
-
},
|
|
433
|
-
},
|
|
434
|
-
},
|
|
435
|
-
{
|
|
436
|
-
name: "find_tests",
|
|
437
|
-
description: "Find tests that exercise a symbol or file via reverse call graph.",
|
|
438
|
-
inputSchema: {
|
|
439
|
-
type: "object",
|
|
440
|
-
properties: {
|
|
441
|
-
target: { type: "string", description: "Symbol name or file path" },
|
|
442
|
-
depth: {
|
|
443
|
-
type: "number",
|
|
444
|
-
description: "Caller traversal depth 1-3 (default 1)",
|
|
445
|
-
},
|
|
446
|
-
},
|
|
447
|
-
required: ["target"],
|
|
448
|
-
},
|
|
449
|
-
},
|
|
450
|
-
{
|
|
451
|
-
name: "impact_analysis",
|
|
452
|
-
description: "Change impact: dependents and affected tests for a symbol or file.",
|
|
453
|
-
inputSchema: {
|
|
454
|
-
type: "object",
|
|
455
|
-
properties: {
|
|
456
|
-
target: { type: "string", description: "Symbol name or file path" },
|
|
457
|
-
depth: {
|
|
458
|
-
type: "number",
|
|
459
|
-
description: "Caller traversal depth 1-3 (default 1)",
|
|
460
|
-
},
|
|
461
|
-
},
|
|
462
|
-
required: ["target"],
|
|
463
|
-
},
|
|
464
|
-
},
|
|
465
|
-
{
|
|
466
|
-
name: "find_similar",
|
|
467
|
-
description: "Find semantically similar code using vector similarity.",
|
|
468
|
-
inputSchema: {
|
|
469
|
-
type: "object",
|
|
470
|
-
properties: {
|
|
471
|
-
target: { type: "string", description: "Symbol name or file path" },
|
|
472
|
-
limit: { type: "number", description: "Max results (default 5)" },
|
|
473
|
-
threshold: {
|
|
474
|
-
type: "number",
|
|
475
|
-
description: "Min similarity 0-1 (default 0)",
|
|
476
|
-
},
|
|
477
|
-
},
|
|
478
|
-
required: ["target"],
|
|
479
|
-
},
|
|
480
|
-
},
|
|
481
|
-
{
|
|
482
|
-
name: "build_context",
|
|
483
|
-
description: "Token-budgeted topic summary (search + skeleton + extract).",
|
|
484
|
-
inputSchema: {
|
|
485
|
-
type: "object",
|
|
486
|
-
properties: {
|
|
487
|
-
topic: {
|
|
488
|
-
type: "string",
|
|
489
|
-
description: "Natural language topic or directory path",
|
|
490
|
-
},
|
|
491
|
-
budget: { type: "number", description: "Max tokens (default 4000)" },
|
|
492
|
-
limit: {
|
|
493
|
-
type: "number",
|
|
494
|
-
description: "Search result limit (default 10)",
|
|
495
|
-
},
|
|
496
|
-
},
|
|
497
|
-
required: ["topic"],
|
|
498
|
-
},
|
|
499
|
-
},
|
|
500
|
-
{
|
|
501
|
-
name: "investigate",
|
|
502
|
-
description: "Agentic codebase Q&A: a local LLM answers questions using search, trace, peek, impact, and related tools. Requires LLM to be enabled (gmax llm on).",
|
|
503
|
-
inputSchema: {
|
|
504
|
-
type: "object",
|
|
505
|
-
properties: {
|
|
506
|
-
question: {
|
|
507
|
-
type: "string",
|
|
508
|
-
description: "Natural language question about the codebase",
|
|
509
|
-
},
|
|
510
|
-
max_rounds: {
|
|
511
|
-
type: "number",
|
|
512
|
-
description: "Max tool-call rounds (default 10)",
|
|
513
|
-
},
|
|
514
|
-
},
|
|
515
|
-
required: ["question"],
|
|
516
|
-
},
|
|
517
|
-
},
|
|
518
|
-
{
|
|
519
|
-
name: "review_commit",
|
|
520
|
-
description: "Review a git commit for bugs, breaking changes, and security issues using local LLM + codebase context. Returns structured findings. Requires LLM to be enabled (gmax llm on).",
|
|
521
|
-
inputSchema: {
|
|
522
|
-
type: "object",
|
|
523
|
-
properties: {
|
|
524
|
-
commit: {
|
|
525
|
-
type: "string",
|
|
526
|
-
description: "Git ref to review (default: HEAD)",
|
|
527
|
-
},
|
|
528
|
-
},
|
|
529
|
-
required: [],
|
|
530
|
-
},
|
|
531
|
-
},
|
|
532
|
-
{
|
|
533
|
-
name: "review_report",
|
|
534
|
-
description: "Get the accumulated code review report for the current project. Returns findings from all reviewed commits.",
|
|
535
|
-
inputSchema: {
|
|
536
|
-
type: "object",
|
|
537
|
-
properties: {
|
|
538
|
-
json: {
|
|
539
|
-
type: "boolean",
|
|
540
|
-
description: "Return raw JSON instead of text (default: false)",
|
|
541
|
-
},
|
|
542
|
-
},
|
|
543
|
-
required: [],
|
|
544
|
-
},
|
|
545
|
-
},
|
|
546
|
-
{
|
|
547
|
-
name: "review_risk",
|
|
548
|
-
description: "Deterministic risk ranking of the symbols a commit's diff touches, ordered by blast radius (inbound callers) × test presence × file churn. No LLM required — fast, graph + git only. Use to triage what a change endangers before a deep review.",
|
|
549
|
-
inputSchema: {
|
|
550
|
-
type: "object",
|
|
551
|
-
properties: {
|
|
552
|
-
commit: {
|
|
553
|
-
type: "string",
|
|
554
|
-
description: "Git ref to analyze (default: HEAD)",
|
|
555
|
-
},
|
|
556
|
-
},
|
|
557
|
-
required: [],
|
|
558
|
-
},
|
|
559
|
-
},
|
|
560
|
-
];
|
|
87
|
+
const audit_1 = require("./audit");
|
|
561
88
|
// ---------------------------------------------------------------------------
|
|
562
89
|
// Helpers
|
|
563
90
|
// ---------------------------------------------------------------------------
|
|
@@ -589,6 +116,23 @@ function chunkSymbols(chunk) {
|
|
|
589
116
|
var _a;
|
|
590
117
|
return toStringArray((_a = chunk.definedSymbols) !== null && _a !== void 0 ? _a : chunk.defined_symbols);
|
|
591
118
|
}
|
|
119
|
+
// searcher.search() returns mapped ChunkType objects: the absolute path lives in
|
|
120
|
+
// metadata.path and line numbers in generated_metadata, never at the top level.
|
|
121
|
+
// Handlers that consume search results must read through these — reading r.path /
|
|
122
|
+
// r.start_line directly yields undefined (and crashed build_context's rel()).
|
|
123
|
+
// Use nullish (not ||) so a legitimate line 0 isn't skipped.
|
|
124
|
+
function searchResultPath(r) {
|
|
125
|
+
var _a, _b, _c;
|
|
126
|
+
return String((_c = (_a = r === null || r === void 0 ? void 0 : r.path) !== null && _a !== void 0 ? _a : (_b = r === null || r === void 0 ? void 0 : r.metadata) === null || _b === void 0 ? void 0 : _b.path) !== null && _c !== void 0 ? _c : "");
|
|
127
|
+
}
|
|
128
|
+
function searchResultStartLine(r) {
|
|
129
|
+
var _a, _b, _c;
|
|
130
|
+
return Number((_c = (_a = r === null || r === void 0 ? void 0 : r.start_line) !== null && _a !== void 0 ? _a : (_b = r === null || r === void 0 ? void 0 : r.generated_metadata) === null || _b === void 0 ? void 0 : _b.start_line) !== null && _c !== void 0 ? _c : 0);
|
|
131
|
+
}
|
|
132
|
+
function searchResultEndLine(r, fallbackStart = 0) {
|
|
133
|
+
var _a, _b, _c;
|
|
134
|
+
return Number((_c = (_a = r === null || r === void 0 ? void 0 : r.end_line) !== null && _a !== void 0 ? _a : (_b = r === null || r === void 0 ? void 0 : r.generated_metadata) === null || _b === void 0 ? void 0 : _b.end_line) !== null && _c !== void 0 ? _c : fallbackStart);
|
|
135
|
+
}
|
|
592
136
|
function filterMcpSearchResults(data, options = {}) {
|
|
593
137
|
var _a, _b;
|
|
594
138
|
let results = data;
|
|
@@ -722,18 +266,34 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
722
266
|
// --- Tool handlers ---
|
|
723
267
|
function handleSemanticSearch(args_1) {
|
|
724
268
|
return __awaiter(this, arguments, void 0, function* (args, isSearchAll = false) {
|
|
725
|
-
var _a, _b;
|
|
726
269
|
const query = String(args.query || "");
|
|
727
270
|
if (!query)
|
|
728
271
|
return err("Missing required parameter: query");
|
|
729
|
-
|
|
272
|
+
let searchAll = isSearchAll || args.scope === "all";
|
|
730
273
|
const limit = Math.min(Math.max(Number(args.limit) || 3, 1), 50);
|
|
731
274
|
ensureWatcher();
|
|
275
|
+
// Project resolution. The server is pinned to whatever cwd it launched in
|
|
276
|
+
// (resolved once at startup). When that cwd ISN'T an indexed project and
|
|
277
|
+
// the caller hasn't pinned an explicit --root, fall back to cross-project
|
|
278
|
+
// search instead of dead-ending — this is what makes the server usable
|
|
279
|
+
// from a session that isn't sitting inside a registered repo.
|
|
280
|
+
let scopeNote = "";
|
|
732
281
|
const proj = (0, project_registry_1.getProject)(projectRoot);
|
|
733
|
-
if (!proj) {
|
|
282
|
+
if (!proj && typeof args.root !== "string") {
|
|
283
|
+
const indexed = (0, project_registry_1.listProjects)().filter((p) => { var _a; return p.status !== "pending" && ((_a = p.chunkCount) !== null && _a !== void 0 ? _a : 0) > 0; });
|
|
284
|
+
if (indexed.length === 0) {
|
|
285
|
+
return err("No indexed projects yet. cd into a repo and run `gmax add` to index it first.");
|
|
286
|
+
}
|
|
287
|
+
searchAll = true;
|
|
288
|
+
scopeNote =
|
|
289
|
+
`Note: ${path.basename(projectRoot)}/ isn't an indexed gmax project — ` +
|
|
290
|
+
`searching all ${indexed.length} indexed project(s). ` +
|
|
291
|
+
`Call list_projects to see them, then pass projects:"name" to narrow.`;
|
|
292
|
+
}
|
|
293
|
+
else if (!proj) {
|
|
734
294
|
return err("Project not added to gmax yet. Run `gmax add` to index it first.");
|
|
735
295
|
}
|
|
736
|
-
if (proj.status === "pending" || proj.chunkCount === 0) {
|
|
296
|
+
else if (proj.status === "pending" || proj.chunkCount === 0) {
|
|
737
297
|
return err("Project not indexed yet. Run `gmax add` to index it first.");
|
|
738
298
|
}
|
|
739
299
|
try {
|
|
@@ -807,10 +367,19 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
807
367
|
};
|
|
808
368
|
const seedFiles = parseSeedList(args.seed_files);
|
|
809
369
|
const seedSymbols = parseSeedList(args.seed_symbols);
|
|
810
|
-
const seeds = seedFiles || seedSymbols
|
|
370
|
+
const seeds = seedFiles || seedSymbols
|
|
371
|
+
? { files: seedFiles, symbols: seedSymbols }
|
|
372
|
+
: undefined;
|
|
811
373
|
const result = yield searcher.search(query, limit, { rerank: process.env.GMAX_RERANK === "1", seeds }, Object.keys(filters).length > 0 ? filters : undefined, pathPrefix);
|
|
374
|
+
// Prepend the scope-fallback note (if any) + searcher warnings to
|
|
375
|
+
// whatever body we return, so the agent learns it searched all projects.
|
|
376
|
+
const prefixNotes = (body) => {
|
|
377
|
+
var _a;
|
|
378
|
+
const notes = [scopeNote, ...((_a = result.warnings) !== null && _a !== void 0 ? _a : [])].filter(Boolean);
|
|
379
|
+
return notes.length ? `${notes.join("\n")}\n\n${body}` : body;
|
|
380
|
+
};
|
|
812
381
|
if (!result.data || result.data.length === 0) {
|
|
813
|
-
return ok("No matches found. Try broadening your query, using fewer keywords, or check `gmax status` to verify the project is indexed.");
|
|
382
|
+
return ok(prefixNotes("No matches found. Try broadening your query, using fewer keywords, or check `gmax status` to verify the project is indexed."));
|
|
814
383
|
}
|
|
815
384
|
const minScore = typeof args.min_score === "number" ? args.min_score : 0;
|
|
816
385
|
const maxPerFile = typeof args.max_per_file === "number" ? args.max_per_file : 0;
|
|
@@ -837,10 +406,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
837
406
|
getImportsForFile,
|
|
838
407
|
query,
|
|
839
408
|
});
|
|
840
|
-
|
|
841
|
-
return ok(`${result.warnings.join("\n")}\n\n${output}`);
|
|
842
|
-
}
|
|
843
|
-
return ok(output);
|
|
409
|
+
return ok(prefixNotes(output));
|
|
844
410
|
}
|
|
845
411
|
let results = result.data.map((r) => {
|
|
846
412
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
@@ -936,7 +502,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
936
502
|
const regex = new RegExp(namePattern, "i");
|
|
937
503
|
results = results.filter((r) => r.symbols.some((s) => regex.test(s)));
|
|
938
504
|
}
|
|
939
|
-
catch (
|
|
505
|
+
catch (_a) {
|
|
940
506
|
// Invalid regex — skip filter
|
|
941
507
|
}
|
|
942
508
|
}
|
|
@@ -979,14 +545,11 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
979
545
|
output += `\n${traceLines.join("\n")}`;
|
|
980
546
|
}
|
|
981
547
|
}
|
|
982
|
-
catch (
|
|
548
|
+
catch (_b) {
|
|
983
549
|
// Trace failed — return search results without trace
|
|
984
550
|
}
|
|
985
551
|
}
|
|
986
|
-
|
|
987
|
-
return ok(`${result.warnings.join("\n")}\n\n${output}`);
|
|
988
|
-
}
|
|
989
|
-
return ok(output);
|
|
552
|
+
return ok(prefixNotes(output));
|
|
990
553
|
}
|
|
991
554
|
catch (e) {
|
|
992
555
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -1210,7 +773,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1210
773
|
"is_exported",
|
|
1211
774
|
"defined_symbols",
|
|
1212
775
|
])
|
|
1213
|
-
.where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbol)}') AND
|
|
776
|
+
.where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbol)}') AND ${(0, filter_builder_1.pathStartsWith)(prefix)}`)
|
|
1214
777
|
.limit(10)
|
|
1215
778
|
.toArray();
|
|
1216
779
|
if (rows.length === 0) {
|
|
@@ -1304,7 +867,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1304
867
|
const metaRows = yield table
|
|
1305
868
|
.query()
|
|
1306
869
|
.select(["is_exported", "start_line", "end_line"])
|
|
1307
|
-
.where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbol)}') AND
|
|
870
|
+
.where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbol)}') AND ${(0, filter_builder_1.pathStartsWith)(prefix)}`)
|
|
1308
871
|
.limit(1)
|
|
1309
872
|
.toArray();
|
|
1310
873
|
const exported = metaRows.length > 0 && Boolean(metaRows[0].is_exported);
|
|
@@ -1419,7 +982,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1419
982
|
const defRows = yield table
|
|
1420
983
|
.query()
|
|
1421
984
|
.select(["path", "start_line", "is_exported"])
|
|
1422
|
-
.where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbol)}') AND
|
|
985
|
+
.where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbol)}') AND ${(0, filter_builder_1.pathStartsWith)(prefix)}`)
|
|
1423
986
|
.limit(1)
|
|
1424
987
|
.toArray();
|
|
1425
988
|
if (defRows.length === 0) {
|
|
@@ -1474,7 +1037,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1474
1037
|
"referenced_symbols",
|
|
1475
1038
|
"is_exported",
|
|
1476
1039
|
])
|
|
1477
|
-
.where(
|
|
1040
|
+
.where((0, filter_builder_1.pathStartsWith)(prefix))
|
|
1478
1041
|
.limit(500000)
|
|
1479
1042
|
.toArray();
|
|
1480
1043
|
if (rows.length === 0) {
|
|
@@ -1658,7 +1221,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1658
1221
|
const absPrefix = path.isAbsolute(pathPrefix)
|
|
1659
1222
|
? pathPrefix
|
|
1660
1223
|
: path.resolve(projectRoot, pathPrefix);
|
|
1661
|
-
query = query.where(
|
|
1224
|
+
query = query.where((0, filter_builder_1.pathStartsWith)((0, filter_builder_1.normalizePath)(absPrefix)));
|
|
1662
1225
|
}
|
|
1663
1226
|
const rows = yield query.toArray();
|
|
1664
1227
|
const map = new Map();
|
|
@@ -1714,6 +1277,32 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1714
1277
|
}
|
|
1715
1278
|
});
|
|
1716
1279
|
}
|
|
1280
|
+
function handleListProjects() {
|
|
1281
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1282
|
+
var _a;
|
|
1283
|
+
try {
|
|
1284
|
+
const projects = (0, project_registry_1.listProjects)();
|
|
1285
|
+
if (projects.length === 0) {
|
|
1286
|
+
return ok("No projects indexed yet. cd into a repo and run `gmax add` to index it.");
|
|
1287
|
+
}
|
|
1288
|
+
const currentName = (_a = (0, project_registry_1.getProject)(projectRoot)) === null || _a === void 0 ? void 0 : _a.name;
|
|
1289
|
+
const lines = projects.map((p) => {
|
|
1290
|
+
const here = p.name === currentName ? " (current)" : "";
|
|
1291
|
+
const chunks = typeof p.chunkCount === "number"
|
|
1292
|
+
? `\t(${p.chunkCount} chunks)`
|
|
1293
|
+
: "";
|
|
1294
|
+
return `${p.name}${here}\t${p.root}\t${p.status}${chunks}`;
|
|
1295
|
+
});
|
|
1296
|
+
return ok(`${projects.length} indexed project(s). ` +
|
|
1297
|
+
`Pass a name to semantic_search via projects:"name", or use scope:"all".\n\n` +
|
|
1298
|
+
lines.join("\n"));
|
|
1299
|
+
}
|
|
1300
|
+
catch (e) {
|
|
1301
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1302
|
+
return err(`Listing projects failed: ${msg}`);
|
|
1303
|
+
}
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1717
1306
|
function handleIndexStatus() {
|
|
1718
1307
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1719
1308
|
var _a, _b, _c, _d, _e, _f;
|
|
@@ -1738,30 +1327,15 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1738
1327
|
watcherLine += " — search results may be incomplete";
|
|
1739
1328
|
}
|
|
1740
1329
|
}
|
|
1330
|
+
// Health overview only. The per-project listing (names, roots,
|
|
1331
|
+
// per-project chunk counts) lives in `list_projects` — keep this tool
|
|
1332
|
+
// focused on index health and avoid the N-project LanceDB scans.
|
|
1741
1333
|
const lines = [
|
|
1742
1334
|
`Index: ~/.gmax/lancedb (${stats.chunks} chunks, ${fileCount} files)`,
|
|
1743
1335
|
`Model: ${globalConfig.embedMode === "gpu" ? ((_d = (_c = (_b = config_1.MODEL_TIERS[globalConfig.modelTier]) === null || _b === void 0 ? void 0 : _b.mlxModel) !== null && _c !== void 0 ? _c : config === null || config === void 0 ? void 0 : config.embedModel) !== null && _d !== void 0 ? _d : "unknown") : ((_e = config === null || config === void 0 ? void 0 : config.embedModel) !== null && _e !== void 0 ? _e : "unknown")} (${(_f = config === null || config === void 0 ? void 0 : config.vectorDim) !== null && _f !== void 0 ? _f : "?"}d, ${globalConfig.embedMode})`,
|
|
1744
1336
|
(config === null || config === void 0 ? void 0 : config.indexedAt) ? `Last indexed: ${config.indexedAt}` : "",
|
|
1745
1337
|
watcherLine,
|
|
1746
|
-
|
|
1747
|
-
"Indexed directories:",
|
|
1748
|
-
...(yield Promise.all(projects.map((p) => __awaiter(this, void 0, void 0, function* () {
|
|
1749
|
-
var _a, _b;
|
|
1750
|
-
const prefix = p.root.endsWith("/") ? p.root : `${p.root}/`;
|
|
1751
|
-
try {
|
|
1752
|
-
const table = yield db.ensureTable();
|
|
1753
|
-
const rows = yield table
|
|
1754
|
-
.query()
|
|
1755
|
-
.select(["id"])
|
|
1756
|
-
.where(`path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`)
|
|
1757
|
-
.limit(100000)
|
|
1758
|
-
.toArray();
|
|
1759
|
-
return ` ${p.name}\t${p.root}\t${(_a = p.lastIndexed) !== null && _a !== void 0 ? _a : "unknown"}\t(${rows.length} chunks)`;
|
|
1760
|
-
}
|
|
1761
|
-
catch (_c) {
|
|
1762
|
-
return ` ${p.name}\t${p.root}\t${(_b = p.lastIndexed) !== null && _b !== void 0 ? _b : "unknown"}`;
|
|
1763
|
-
}
|
|
1764
|
-
})))),
|
|
1338
|
+
`Projects: ${projects.length} indexed (call list_projects for names + per-project chunk counts)`,
|
|
1765
1339
|
].filter(Boolean);
|
|
1766
1340
|
return ok(lines.join("\n"));
|
|
1767
1341
|
}
|
|
@@ -1814,7 +1388,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1814
1388
|
"defined_symbols",
|
|
1815
1389
|
"referenced_symbols",
|
|
1816
1390
|
])
|
|
1817
|
-
.where(
|
|
1391
|
+
.where((0, filter_builder_1.pathStartsWith)(prefix))
|
|
1818
1392
|
.limit(200000)
|
|
1819
1393
|
.toArray();
|
|
1820
1394
|
if (rows.length === 0) {
|
|
@@ -2092,7 +1666,9 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2092
1666
|
const searcher = getSearcher();
|
|
2093
1667
|
const response = yield searcher.search(query, limit, { rerank: process.env.GMAX_RERANK === "1" }, {}, projectRoot);
|
|
2094
1668
|
const changedSet = new Set(changedFiles);
|
|
2095
|
-
|
|
1669
|
+
// searcher.search() returns mapped chunks (path under metadata.path);
|
|
1670
|
+
// changedFiles are absolute, so match on the resolved absolute path.
|
|
1671
|
+
let filtered = response.data.filter((r) => changedSet.has(searchResultPath(r)));
|
|
2096
1672
|
if (role)
|
|
2097
1673
|
filtered = filtered.filter((r) => String(r.role || "")
|
|
2098
1674
|
.toUpperCase()
|
|
@@ -2100,9 +1676,9 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2100
1676
|
if (filtered.length === 0)
|
|
2101
1677
|
return ok("No indexed results found in changed files for that query.");
|
|
2102
1678
|
const lines = filtered.slice(0, limit).map((r) => {
|
|
2103
|
-
var _a, _b
|
|
1679
|
+
var _a, _b;
|
|
2104
1680
|
const sym = (_b = (_a = toStringArray(r.defined_symbols)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : "";
|
|
2105
|
-
return `${rel(r
|
|
1681
|
+
return `${rel(searchResultPath(r))}:${searchResultStartLine(r) + 1} ${sym} [${r.role || "IMPL"}]`;
|
|
2106
1682
|
});
|
|
2107
1683
|
return ok(lines.join("\n"));
|
|
2108
1684
|
}
|
|
@@ -2253,7 +1829,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2253
1829
|
"role",
|
|
2254
1830
|
"_distance",
|
|
2255
1831
|
])
|
|
2256
|
-
.where(
|
|
1832
|
+
.where((0, filter_builder_1.pathStartsWith)(`${projectRoot}/`))
|
|
2257
1833
|
.limit(limit + 5)
|
|
2258
1834
|
.toArray();
|
|
2259
1835
|
let filtered = results.filter((r) => !(r.path === source.path && r.start_line === source.start_line));
|
|
@@ -2276,7 +1852,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2276
1852
|
}
|
|
2277
1853
|
function handleBuildContext(args) {
|
|
2278
1854
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2279
|
-
var _a, _b
|
|
1855
|
+
var _a, _b;
|
|
2280
1856
|
ensureWatcher();
|
|
2281
1857
|
const topic = String(args.topic || "");
|
|
2282
1858
|
if (!topic)
|
|
@@ -2292,21 +1868,22 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2292
1868
|
const estTokens = (s) => Math.ceil(s.length / 4);
|
|
2293
1869
|
let tokensUsed = 0;
|
|
2294
1870
|
const sections = [];
|
|
2295
|
-
// Entry points
|
|
1871
|
+
// Entry points. searcher.search() returns mapped chunks, so the path and
|
|
1872
|
+
// line live under metadata/generated_metadata — read via the helpers.
|
|
2296
1873
|
const epLines = response.data.slice(0, 5).map((r) => {
|
|
2297
|
-
var _a, _b
|
|
1874
|
+
var _a, _b;
|
|
2298
1875
|
const sym = (_b = (_a = toStringArray(r.defined_symbols)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : "";
|
|
2299
|
-
return `${rel(r
|
|
1876
|
+
return `${rel(searchResultPath(r))}:${searchResultStartLine(r) + 1} ${sym} [${r.role || "IMPL"}]`;
|
|
2300
1877
|
});
|
|
2301
1878
|
const epSection = `## Entry Points\n${epLines.join("\n")}`;
|
|
2302
1879
|
sections.push(epSection);
|
|
2303
1880
|
tokensUsed += estTokens(epSection);
|
|
2304
1881
|
// Key function bodies
|
|
2305
1882
|
for (const r of response.data.slice(0, 3)) {
|
|
2306
|
-
const absP =
|
|
2307
|
-
const startLine =
|
|
2308
|
-
const endLine =
|
|
2309
|
-
const sym = (
|
|
1883
|
+
const absP = searchResultPath(r);
|
|
1884
|
+
const startLine = searchResultStartLine(r);
|
|
1885
|
+
const endLine = searchResultEndLine(r, startLine);
|
|
1886
|
+
const sym = (_b = (_a = toStringArray(r.defined_symbols)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : "";
|
|
2310
1887
|
try {
|
|
2311
1888
|
const content = fs.readFileSync(absP, "utf-8");
|
|
2312
1889
|
const body = content
|
|
@@ -2319,7 +1896,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2319
1896
|
sections.push(blob);
|
|
2320
1897
|
tokensUsed += estTokens(blob);
|
|
2321
1898
|
}
|
|
2322
|
-
catch (
|
|
1899
|
+
catch (_c) {
|
|
2323
1900
|
/* skip unreadable */
|
|
2324
1901
|
}
|
|
2325
1902
|
}
|
|
@@ -2331,219 +1908,514 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2331
1908
|
}
|
|
2332
1909
|
});
|
|
2333
1910
|
}
|
|
2334
|
-
// ---
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
return { tools: TOOLS };
|
|
2348
|
-
}));
|
|
2349
|
-
server.setRequestHandler(types_js_1.CallToolRequestSchema, (request) => __awaiter(void 0, void 0, void 0, function* () {
|
|
2350
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
2351
|
-
const { name, arguments: args } = request.params;
|
|
2352
|
-
const toolArgs = (args !== null && args !== void 0 ? args : {});
|
|
2353
|
-
const startMs = Date.now();
|
|
2354
|
-
let result;
|
|
2355
|
-
switch (name) {
|
|
2356
|
-
case "semantic_search":
|
|
2357
|
-
result = yield handleSemanticSearch(toolArgs, false);
|
|
2358
|
-
break;
|
|
2359
|
-
case "code_skeleton":
|
|
2360
|
-
result = yield handleCodeSkeleton(toolArgs);
|
|
2361
|
-
break;
|
|
2362
|
-
case "trace_calls":
|
|
2363
|
-
result = yield handleTraceCalls(toolArgs);
|
|
2364
|
-
break;
|
|
2365
|
-
case "extract_symbol":
|
|
2366
|
-
result = yield handleExtractSymbol(toolArgs);
|
|
2367
|
-
break;
|
|
2368
|
-
case "peek_symbol":
|
|
2369
|
-
result = yield handlePeekSymbol(toolArgs);
|
|
2370
|
-
break;
|
|
2371
|
-
case "dead":
|
|
2372
|
-
result = yield handleDead(toolArgs);
|
|
2373
|
-
break;
|
|
2374
|
-
case "audit":
|
|
2375
|
-
result = yield handleAudit(toolArgs);
|
|
2376
|
-
break;
|
|
2377
|
-
case "get_neighbors":
|
|
2378
|
-
result = yield handleGetNeighbors(toolArgs);
|
|
2379
|
-
break;
|
|
2380
|
-
case "find_paths":
|
|
2381
|
-
result = yield handleFindPaths(toolArgs);
|
|
2382
|
-
break;
|
|
2383
|
-
case "subgraph_for_files":
|
|
2384
|
-
result = yield handleSubgraphForFiles(toolArgs);
|
|
2385
|
-
break;
|
|
2386
|
-
case "list_symbols":
|
|
2387
|
-
result = yield handleListSymbols(toolArgs);
|
|
2388
|
-
break;
|
|
2389
|
-
case "index_status":
|
|
2390
|
-
result = yield handleIndexStatus();
|
|
2391
|
-
break;
|
|
2392
|
-
case "summarize_directory":
|
|
2393
|
-
result = yield handleSummarizeDirectory(toolArgs);
|
|
2394
|
-
break;
|
|
2395
|
-
case "summarize_project":
|
|
2396
|
-
result = yield handleSummarizeProject(toolArgs);
|
|
2397
|
-
break;
|
|
2398
|
-
case "related_files":
|
|
2399
|
-
result = yield handleRelatedFiles(toolArgs);
|
|
2400
|
-
break;
|
|
2401
|
-
case "recent_changes":
|
|
2402
|
-
result = yield handleRecentChanges(toolArgs);
|
|
2403
|
-
break;
|
|
2404
|
-
case "diff_changes":
|
|
2405
|
-
result = yield handleDiffChanges(toolArgs);
|
|
2406
|
-
break;
|
|
2407
|
-
case "find_tests":
|
|
2408
|
-
result = yield handleFindTests(toolArgs);
|
|
2409
|
-
break;
|
|
2410
|
-
case "impact_analysis":
|
|
2411
|
-
result = yield handleImpactAnalysis(toolArgs);
|
|
2412
|
-
break;
|
|
2413
|
-
case "find_similar":
|
|
2414
|
-
result = yield handleFindSimilar(toolArgs);
|
|
2415
|
-
break;
|
|
2416
|
-
case "build_context":
|
|
2417
|
-
result = yield handleBuildContext(toolArgs);
|
|
2418
|
-
break;
|
|
2419
|
-
case "investigate": {
|
|
2420
|
-
const question = String(toolArgs.question || "");
|
|
2421
|
-
if (!question) {
|
|
2422
|
-
result = err("Missing required parameter: question");
|
|
2423
|
-
break;
|
|
2424
|
-
}
|
|
2425
|
-
const maxRounds = Math.min(Math.max(Number(toolArgs.max_rounds) || 10, 1), 15);
|
|
2426
|
-
try {
|
|
2427
|
-
const { isDaemonRunning, sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
|
|
2428
|
-
if (yield isDaemonRunning()) {
|
|
2429
|
-
const llmResp = yield sendDaemonCommand({ cmd: "llm-start" }, { timeoutMs: 90000 });
|
|
2430
|
-
if (!llmResp.ok) {
|
|
2431
|
-
result = err(`LLM server not available: ${llmResp.error}. Run \`gmax llm on && gmax llm start\`.`);
|
|
2432
|
-
break;
|
|
2433
|
-
}
|
|
2434
|
-
}
|
|
2435
|
-
else {
|
|
2436
|
-
result = err("LLM server not available. Run `gmax llm on && gmax llm start`.");
|
|
2437
|
-
break;
|
|
1911
|
+
// --- Review/LLM tool handlers (extracted from the old dispatch switch) ---
|
|
1912
|
+
function handleInvestigate(args) {
|
|
1913
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1914
|
+
const question = String(args.question || "");
|
|
1915
|
+
if (!question)
|
|
1916
|
+
return err("Missing required parameter: question");
|
|
1917
|
+
const maxRounds = Math.min(Math.max(Number(args.max_rounds) || 10, 1), 15);
|
|
1918
|
+
try {
|
|
1919
|
+
const { isDaemonRunning, sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
|
|
1920
|
+
if (yield isDaemonRunning()) {
|
|
1921
|
+
const llmResp = yield sendDaemonCommand({ cmd: "llm-start" }, { timeoutMs: 90000 });
|
|
1922
|
+
if (!llmResp.ok) {
|
|
1923
|
+
return err(`LLM server not available: ${llmResp.error}. Run \`gmax llm on && gmax llm start\`.`);
|
|
2438
1924
|
}
|
|
2439
|
-
const { investigate } = yield Promise.resolve().then(() => __importStar(require("../lib/llm/investigate")));
|
|
2440
|
-
const inv = yield investigate({ question, projectRoot, maxRounds });
|
|
2441
|
-
result = ok(inv.answer);
|
|
2442
1925
|
}
|
|
2443
|
-
|
|
2444
|
-
|
|
1926
|
+
else {
|
|
1927
|
+
return err("LLM server not available. Run `gmax llm on && gmax llm start`.");
|
|
2445
1928
|
}
|
|
2446
|
-
|
|
1929
|
+
const { investigate } = yield Promise.resolve().then(() => __importStar(require("../lib/llm/investigate")));
|
|
1930
|
+
const inv = yield investigate({ question, projectRoot, maxRounds });
|
|
1931
|
+
return ok(inv.answer);
|
|
2447
1932
|
}
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
const { reviewCommit } = yield Promise.resolve().then(() => __importStar(require("../lib/llm/review")));
|
|
2464
|
-
const rev = yield reviewCommit({ commitRef, projectRoot });
|
|
2465
|
-
if (rev.clean) {
|
|
2466
|
-
result = ok(`Clean commit (${rev.commit}) — no issues found in ${rev.duration}s.`);
|
|
2467
|
-
}
|
|
2468
|
-
else {
|
|
2469
|
-
const { readReport } = yield Promise.resolve().then(() => __importStar(require("../lib/llm/report")));
|
|
2470
|
-
const report = readReport(projectRoot);
|
|
2471
|
-
const entry = report === null || report === void 0 ? void 0 : report.reviews.find((r) => r.commit === rev.commit);
|
|
2472
|
-
result = ok(JSON.stringify({
|
|
2473
|
-
commit: rev.commit,
|
|
2474
|
-
findings: (_a = entry === null || entry === void 0 ? void 0 : entry.findings) !== null && _a !== void 0 ? _a : [],
|
|
2475
|
-
duration: rev.duration,
|
|
2476
|
-
}, null, 2));
|
|
1933
|
+
catch (e) {
|
|
1934
|
+
return err(`Investigate failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1935
|
+
}
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
function handleReviewCommit(args) {
|
|
1939
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1940
|
+
var _a;
|
|
1941
|
+
const commitRef = String(args.commit || "HEAD");
|
|
1942
|
+
try {
|
|
1943
|
+
const { isDaemonRunning, sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
|
|
1944
|
+
if (yield isDaemonRunning()) {
|
|
1945
|
+
const llmResp = yield sendDaemonCommand({ cmd: "llm-start" }, { timeoutMs: 90000 });
|
|
1946
|
+
if (!llmResp.ok) {
|
|
1947
|
+
return err(`LLM server not available: ${llmResp.error}. Run \`gmax llm on && gmax llm start\`.`);
|
|
2477
1948
|
}
|
|
2478
1949
|
}
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
}
|
|
2482
|
-
|
|
1950
|
+
else {
|
|
1951
|
+
return err("LLM server not available. Run `gmax llm on && gmax llm start`.");
|
|
1952
|
+
}
|
|
1953
|
+
const { reviewCommit } = yield Promise.resolve().then(() => __importStar(require("../lib/llm/review")));
|
|
1954
|
+
const rev = yield reviewCommit({ commitRef, projectRoot });
|
|
1955
|
+
if (rev.clean) {
|
|
1956
|
+
return ok(`Clean commit (${rev.commit}) — no issues found in ${rev.duration}s.`);
|
|
1957
|
+
}
|
|
1958
|
+
const { readReport } = yield Promise.resolve().then(() => __importStar(require("../lib/llm/report")));
|
|
1959
|
+
const report = readReport(projectRoot);
|
|
1960
|
+
const entry = report === null || report === void 0 ? void 0 : report.reviews.find((r) => r.commit === rev.commit);
|
|
1961
|
+
return ok(JSON.stringify({
|
|
1962
|
+
commit: rev.commit,
|
|
1963
|
+
findings: (_a = entry === null || entry === void 0 ? void 0 : entry.findings) !== null && _a !== void 0 ? _a : [],
|
|
1964
|
+
duration: rev.duration,
|
|
1965
|
+
}, null, 2));
|
|
2483
1966
|
}
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
const commitRef = String(toolArgs.commit || "HEAD");
|
|
2487
|
-
try {
|
|
2488
|
-
const db = getVectorDb();
|
|
2489
|
-
const builder = new graph_builder_1.GraphBuilder(db, projectRoot);
|
|
2490
|
-
const { gatherRiskInputs, computeRiskTable, formatRiskTable } = yield Promise.resolve().then(() => __importStar(require("../lib/review/risk")));
|
|
2491
|
-
const inputs = yield gatherRiskInputs(commitRef, projectRoot, {
|
|
2492
|
-
vectorDb: db,
|
|
2493
|
-
graphBuilder: builder,
|
|
2494
|
-
});
|
|
2495
|
-
const rows = computeRiskTable(inputs);
|
|
2496
|
-
result =
|
|
2497
|
-
rows.length === 0
|
|
2498
|
-
? ok("(no changed symbols in this diff)")
|
|
2499
|
-
: ok(formatRiskTable(rows, { agent: true }));
|
|
2500
|
-
}
|
|
2501
|
-
catch (e) {
|
|
2502
|
-
result = err(`Risk ranking failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
2503
|
-
}
|
|
2504
|
-
break;
|
|
1967
|
+
catch (e) {
|
|
1968
|
+
return err(`Review failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
2505
1969
|
}
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
function handleReviewRisk(args) {
|
|
1973
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1974
|
+
ensureWatcher();
|
|
1975
|
+
const commitRef = String(args.commit || "HEAD");
|
|
1976
|
+
try {
|
|
1977
|
+
const db = getVectorDb();
|
|
1978
|
+
const builder = new graph_builder_1.GraphBuilder(db, projectRoot);
|
|
1979
|
+
const { gatherRiskInputs, computeRiskTable, formatRiskTable } = yield Promise.resolve().then(() => __importStar(require("../lib/review/risk")));
|
|
1980
|
+
const inputs = yield gatherRiskInputs(commitRef, projectRoot, {
|
|
1981
|
+
vectorDb: db,
|
|
1982
|
+
graphBuilder: builder,
|
|
1983
|
+
});
|
|
1984
|
+
const rows = computeRiskTable(inputs);
|
|
1985
|
+
return rows.length === 0
|
|
1986
|
+
? ok("(no changed symbols in this diff)")
|
|
1987
|
+
: ok(formatRiskTable(rows, { agent: true }));
|
|
1988
|
+
}
|
|
1989
|
+
catch (e) {
|
|
1990
|
+
return err(`Risk ranking failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1991
|
+
}
|
|
1992
|
+
});
|
|
1993
|
+
}
|
|
1994
|
+
function handleReviewReport(args) {
|
|
1995
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1996
|
+
try {
|
|
1997
|
+
const { readReport, formatReportText } = yield Promise.resolve().then(() => __importStar(require("../lib/llm/report")));
|
|
1998
|
+
const report = readReport(projectRoot);
|
|
1999
|
+
if (!report || report.reviews.length === 0) {
|
|
2000
|
+
return ok("No review findings yet.");
|
|
2519
2001
|
}
|
|
2520
|
-
|
|
2521
|
-
|
|
2002
|
+
if (args.json) {
|
|
2003
|
+
return ok(JSON.stringify(report, null, 2));
|
|
2522
2004
|
}
|
|
2523
|
-
|
|
2005
|
+
return ok(formatReportText(report));
|
|
2524
2006
|
}
|
|
2525
|
-
|
|
2526
|
-
return err(`
|
|
2527
|
-
|
|
2528
|
-
|
|
2007
|
+
catch (e) {
|
|
2008
|
+
return err(`Report failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
2009
|
+
}
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
2012
|
+
// --- MCP server setup ---
|
|
2013
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
2014
|
+
const server = new mcp_js_1.McpServer({
|
|
2015
|
+
name: "gmax",
|
|
2016
|
+
version: JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json"), {
|
|
2017
|
+
encoding: "utf-8",
|
|
2018
|
+
})).version,
|
|
2019
|
+
}, {
|
|
2020
|
+
capabilities: {
|
|
2021
|
+
tools: {},
|
|
2022
|
+
},
|
|
2023
|
+
instructions: "gmax is a local semantic code-search index over one or more projects. " +
|
|
2024
|
+
"By default tools operate on the project at the server's working directory. " +
|
|
2025
|
+
"If you are NOT inside a specific repo, or want a different one: call " +
|
|
2026
|
+
'`list_projects` to see what\'s indexed, then pass `projects:"name"` (or ' +
|
|
2027
|
+
'`scope:"all"`) to `semantic_search`. When the working directory isn\'t an ' +
|
|
2028
|
+
"indexed project, `semantic_search` automatically searches all of them and " +
|
|
2029
|
+
"says so. Prefer `semantic_search` (detail:'pointer') for discovery, then " +
|
|
2030
|
+
"`extract_symbol`/`peek_symbol`/`code_skeleton` to read, and " +
|
|
2031
|
+
"`trace_calls`/`impact_analysis`/`find_tests` for call-graph questions.",
|
|
2032
|
+
});
|
|
2033
|
+
// Best-effort query logging, applied uniformly to every tool exactly as the
|
|
2034
|
+
// old single CallToolRequestSchema dispatch did before each return.
|
|
2035
|
+
const logToolCall = (name, toolArgs, startMs, result) => __awaiter(void 0, void 0, void 0, function* () {
|
|
2036
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2529
2037
|
try {
|
|
2530
2038
|
const { logQuery } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/query-log")));
|
|
2531
|
-
const text = (
|
|
2039
|
+
const text = (_c = (_b = (_a = result.content) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.text) !== null && _c !== void 0 ? _c : "";
|
|
2532
2040
|
const resultLines = text.split("\n").filter((l) => l.trim()).length;
|
|
2533
2041
|
logQuery({
|
|
2534
2042
|
ts: new Date().toISOString(),
|
|
2535
2043
|
source: "mcp",
|
|
2536
2044
|
tool: name,
|
|
2537
|
-
query: String((
|
|
2045
|
+
query: String((_f = (_e = (_d = toolArgs.query) !== null && _d !== void 0 ? _d : toolArgs.symbol) !== null && _e !== void 0 ? _e : toolArgs.target) !== null && _f !== void 0 ? _f : ""),
|
|
2538
2046
|
project: projectRoot,
|
|
2539
2047
|
results: resultLines,
|
|
2540
2048
|
ms: Date.now() - startMs,
|
|
2541
2049
|
error: result.isError ? text.slice(0, 200) : undefined,
|
|
2542
2050
|
});
|
|
2543
2051
|
}
|
|
2544
|
-
catch (
|
|
2545
|
-
|
|
2546
|
-
|
|
2052
|
+
catch (_g) { }
|
|
2053
|
+
});
|
|
2054
|
+
// Register a tool, wrapping its handler with timing + query logging. Zod raw
|
|
2055
|
+
// shapes give us free input validation (the SDK rejects calls that violate
|
|
2056
|
+
// the schema before the handler runs); handlers stay defensively coded.
|
|
2057
|
+
function tool(name, config, handler) {
|
|
2058
|
+
server.registerTool(name, config, (rawArgs) => __awaiter(this, void 0, void 0, function* () {
|
|
2059
|
+
const args = (rawArgs !== null && rawArgs !== void 0 ? rawArgs : {});
|
|
2060
|
+
const startMs = Date.now();
|
|
2061
|
+
const result = yield handler(args);
|
|
2062
|
+
yield logToolCall(name, args, startMs, result);
|
|
2063
|
+
return result;
|
|
2064
|
+
}));
|
|
2065
|
+
}
|
|
2066
|
+
tool("semantic_search", {
|
|
2067
|
+
description: "Search code by meaning. Use scope:'all' for cross-project. Prefer CLI: gmax \"query\" --plain",
|
|
2068
|
+
inputSchema: {
|
|
2069
|
+
query: zod_1.z
|
|
2070
|
+
.string()
|
|
2071
|
+
.describe("Natural language query (5+ words recommended)"),
|
|
2072
|
+
limit: zod_1.z
|
|
2073
|
+
.number()
|
|
2074
|
+
.describe("Max results (default 3, max 50)")
|
|
2075
|
+
.optional(),
|
|
2076
|
+
root: zod_1.z
|
|
2077
|
+
.string()
|
|
2078
|
+
.describe("Search a different directory (absolute path)")
|
|
2079
|
+
.optional(),
|
|
2080
|
+
path: zod_1.z
|
|
2081
|
+
.string()
|
|
2082
|
+
.describe("Path prefix filter (e.g. 'src/auth/')")
|
|
2083
|
+
.optional(),
|
|
2084
|
+
detail: zod_1.z
|
|
2085
|
+
.string()
|
|
2086
|
+
.describe("'pointer' (default), 'code', or 'full'")
|
|
2087
|
+
.optional(),
|
|
2088
|
+
min_score: zod_1.z
|
|
2089
|
+
.number()
|
|
2090
|
+
.describe("Min score 0-1 (default 0)")
|
|
2091
|
+
.optional(),
|
|
2092
|
+
max_per_file: zod_1.z.number().describe("Max results per file").optional(),
|
|
2093
|
+
file: zod_1.z
|
|
2094
|
+
.string()
|
|
2095
|
+
.describe("Filename filter (e.g. 'syncer.ts')")
|
|
2096
|
+
.optional(),
|
|
2097
|
+
exclude: zod_1.z
|
|
2098
|
+
.string()
|
|
2099
|
+
.describe("Exclude path prefix (e.g. 'tests/')")
|
|
2100
|
+
.optional(),
|
|
2101
|
+
language: zod_1.z
|
|
2102
|
+
.string()
|
|
2103
|
+
.describe("Extension filter (e.g. 'ts', 'py')")
|
|
2104
|
+
.optional(),
|
|
2105
|
+
role: zod_1.z
|
|
2106
|
+
.string()
|
|
2107
|
+
.describe("'ORCHESTRATION', 'DEFINITION', or 'IMPLEMENTATION'")
|
|
2108
|
+
.optional(),
|
|
2109
|
+
context_lines: zod_1.z
|
|
2110
|
+
.number()
|
|
2111
|
+
.describe("Lines before/after chunk (max 20)")
|
|
2112
|
+
.optional(),
|
|
2113
|
+
mode: zod_1.z
|
|
2114
|
+
.string()
|
|
2115
|
+
.describe("'default' or 'symbol' (appends call graph)")
|
|
2116
|
+
.optional(),
|
|
2117
|
+
include_imports: zod_1.z
|
|
2118
|
+
.boolean()
|
|
2119
|
+
.describe("Prepend file imports to results")
|
|
2120
|
+
.optional(),
|
|
2121
|
+
name_pattern: zod_1.z
|
|
2122
|
+
.string()
|
|
2123
|
+
.describe("Regex filter on symbol name")
|
|
2124
|
+
.optional(),
|
|
2125
|
+
scope: zod_1.z
|
|
2126
|
+
.string()
|
|
2127
|
+
.describe("'project' (default) or 'all' (search everything)")
|
|
2128
|
+
.optional(),
|
|
2129
|
+
projects: zod_1.z
|
|
2130
|
+
.string()
|
|
2131
|
+
.describe("Project names to include (comma-separated)")
|
|
2132
|
+
.optional(),
|
|
2133
|
+
exclude_projects: zod_1.z
|
|
2134
|
+
.string()
|
|
2135
|
+
.describe("Project names to exclude (comma-separated)")
|
|
2136
|
+
.optional(),
|
|
2137
|
+
seed_files: zod_1.z
|
|
2138
|
+
.string()
|
|
2139
|
+
.describe("Bias results toward your working context: comma-separated paths you have open (e.g. 'src/lib/llm/server.ts'). On-topic chunks in these files get lifted; off-topic ones are not.")
|
|
2140
|
+
.optional(),
|
|
2141
|
+
seed_symbols: zod_1.z
|
|
2142
|
+
.string()
|
|
2143
|
+
.describe("Bias results toward identifiers you're working with: comma-separated symbol names. Chunks defining a seeded symbol are preferred over mere callers.")
|
|
2144
|
+
.optional(),
|
|
2145
|
+
},
|
|
2146
|
+
}, handleSemanticSearch);
|
|
2147
|
+
tool("code_skeleton", {
|
|
2148
|
+
description: "File structure with bodies collapsed (~4x fewer tokens). Accepts file, directory, or comma-separated paths.",
|
|
2149
|
+
inputSchema: {
|
|
2150
|
+
target: zod_1.z
|
|
2151
|
+
.string()
|
|
2152
|
+
.describe("File, directory, or comma-separated paths"),
|
|
2153
|
+
limit: zod_1.z
|
|
2154
|
+
.number()
|
|
2155
|
+
.describe("Max files for directory mode (default 10)")
|
|
2156
|
+
.optional(),
|
|
2157
|
+
format: zod_1.z.string().describe("'text' (default) or 'json'").optional(),
|
|
2158
|
+
},
|
|
2159
|
+
}, handleCodeSkeleton);
|
|
2160
|
+
tool("trace_calls", {
|
|
2161
|
+
description: "Call graph: importers, callers (multi-hop), callees with file:line.",
|
|
2162
|
+
inputSchema: {
|
|
2163
|
+
symbol: zod_1.z.string().describe("Function/class name to trace"),
|
|
2164
|
+
depth: zod_1.z
|
|
2165
|
+
.number()
|
|
2166
|
+
.describe("Caller depth (default 1, max 3)")
|
|
2167
|
+
.optional(),
|
|
2168
|
+
},
|
|
2169
|
+
}, handleTraceCalls);
|
|
2170
|
+
tool("extract_symbol", {
|
|
2171
|
+
description: "Extract complete function/class body by symbol name.",
|
|
2172
|
+
inputSchema: {
|
|
2173
|
+
symbol: zod_1.z.string().describe("Symbol name to extract"),
|
|
2174
|
+
root: zod_1.z.string().describe("Project root (absolute path)").optional(),
|
|
2175
|
+
include_imports: zod_1.z
|
|
2176
|
+
.boolean()
|
|
2177
|
+
.describe("Prepend file imports")
|
|
2178
|
+
.optional(),
|
|
2179
|
+
},
|
|
2180
|
+
}, handleExtractSymbol);
|
|
2181
|
+
tool("peek_symbol", {
|
|
2182
|
+
description: "Compact symbol overview: signature + callers + callees.",
|
|
2183
|
+
inputSchema: {
|
|
2184
|
+
symbol: zod_1.z.string().describe("Symbol name"),
|
|
2185
|
+
root: zod_1.z.string().describe("Project root (absolute path)").optional(),
|
|
2186
|
+
depth: zod_1.z
|
|
2187
|
+
.number()
|
|
2188
|
+
.describe("Caller depth (default 1, max 3)")
|
|
2189
|
+
.optional(),
|
|
2190
|
+
},
|
|
2191
|
+
}, handlePeekSymbol);
|
|
2192
|
+
tool("dead", {
|
|
2193
|
+
description: "Report whether a symbol has zero inbound callers in the indexed call graph. Returns DEAD, PUBLIC EXPORT (exported with no internal callers), or LIVE with caller count. Hypothesis, not proof: dynamic dispatch, reflection, and string-built call sites won't show up.",
|
|
2194
|
+
inputSchema: {
|
|
2195
|
+
symbol: zod_1.z.string().describe("Symbol name to check"),
|
|
2196
|
+
root: zod_1.z.string().describe("Project root (absolute path)").optional(),
|
|
2197
|
+
},
|
|
2198
|
+
}, handleDead);
|
|
2199
|
+
tool("audit", {
|
|
2200
|
+
description: "Graph-summary of the indexed project in one call: god nodes (most depended-upon symbols), hub files (most depended-upon files), and dead-code candidates (non-exported symbols with zero inbound references). Built from the static call graph — dynamic dispatch, reflection, eval, and type-position-only references are invisible, so dead candidates are hypotheses; verify with the `dead` tool before acting.",
|
|
2201
|
+
inputSchema: {
|
|
2202
|
+
root: zod_1.z
|
|
2203
|
+
.string()
|
|
2204
|
+
.describe("Project root (default: current)")
|
|
2205
|
+
.optional(),
|
|
2206
|
+
top: zod_1.z
|
|
2207
|
+
.number()
|
|
2208
|
+
.describe("How many of each category to return (default 10)")
|
|
2209
|
+
.optional(),
|
|
2210
|
+
},
|
|
2211
|
+
}, handleAudit);
|
|
2212
|
+
tool("get_neighbors", {
|
|
2213
|
+
description: "Graph primitive: symbols reachable from a node along call edges within N hops. direction 'callees' = what it calls (outbound), 'callers' = what calls it (inbound). Each result carries its hop distance and definition location. Static call graph — same caveats as `dead`/`audit`.",
|
|
2214
|
+
inputSchema: {
|
|
2215
|
+
symbol: zod_1.z.string().describe("Starting symbol"),
|
|
2216
|
+
direction: zod_1.z
|
|
2217
|
+
.enum(["callers", "callees"])
|
|
2218
|
+
.describe("Edge direction (default callees)")
|
|
2219
|
+
.optional(),
|
|
2220
|
+
max_hops: zod_1.z
|
|
2221
|
+
.number()
|
|
2222
|
+
.describe("Max hops (default 2, max 5)")
|
|
2223
|
+
.optional(),
|
|
2224
|
+
root: zod_1.z.string().describe("Project root (absolute path)").optional(),
|
|
2225
|
+
},
|
|
2226
|
+
}, handleGetNeighbors);
|
|
2227
|
+
tool("find_paths", {
|
|
2228
|
+
description: "Graph primitive: shortest call-graph path between two symbols, as a symbol sequence. direction 'callees' searches outward from `from` (does `from` transitively call `to`?), 'callers' searches inward. Returns 'no path' if unreachable within max_hops.",
|
|
2229
|
+
inputSchema: {
|
|
2230
|
+
from: zod_1.z.string().describe("Start symbol"),
|
|
2231
|
+
to: zod_1.z.string().describe("Target symbol"),
|
|
2232
|
+
direction: zod_1.z
|
|
2233
|
+
.enum(["callers", "callees"])
|
|
2234
|
+
.describe("Search direction (default callees)")
|
|
2235
|
+
.optional(),
|
|
2236
|
+
max_hops: zod_1.z
|
|
2237
|
+
.number()
|
|
2238
|
+
.describe("Max hops (default 6, max 10)")
|
|
2239
|
+
.optional(),
|
|
2240
|
+
root: zod_1.z.string().describe("Project root (absolute path)").optional(),
|
|
2241
|
+
},
|
|
2242
|
+
}, handleFindPaths);
|
|
2243
|
+
tool("subgraph_for_files", {
|
|
2244
|
+
description: "Graph primitive: the local dependency subgraph for a set of files — symbols they define, the call edges among those symbols, and their outbound external dependencies. Paths may be absolute or project-root-relative.",
|
|
2245
|
+
inputSchema: {
|
|
2246
|
+
files: zod_1.z
|
|
2247
|
+
.array(zod_1.z.string())
|
|
2248
|
+
.describe("File paths (absolute or root-relative)"),
|
|
2249
|
+
root: zod_1.z.string().describe("Project root (absolute path)").optional(),
|
|
2250
|
+
},
|
|
2251
|
+
}, handleSubgraphForFiles);
|
|
2252
|
+
tool("list_symbols", {
|
|
2253
|
+
description: "List indexed symbols with role and export status.",
|
|
2254
|
+
inputSchema: {
|
|
2255
|
+
pattern: zod_1.z
|
|
2256
|
+
.string()
|
|
2257
|
+
.describe("Name filter (case-insensitive)")
|
|
2258
|
+
.optional(),
|
|
2259
|
+
limit: zod_1.z.number().describe("Max results (default 20)").optional(),
|
|
2260
|
+
path: zod_1.z.string().describe("Path prefix filter").optional(),
|
|
2261
|
+
},
|
|
2262
|
+
}, handleListSymbols);
|
|
2263
|
+
tool("index_status", {
|
|
2264
|
+
description: "Index health: chunks, files, projects, watcher status.",
|
|
2265
|
+
inputSchema: {},
|
|
2266
|
+
_meta: { "anthropic/alwaysLoad": true },
|
|
2267
|
+
}, handleIndexStatus);
|
|
2268
|
+
tool("list_projects", {
|
|
2269
|
+
description: "List every gmax-indexed project (name, root, status, chunk count). Use this to pick a search scope when you're not inside a specific repo: feed a name into semantic_search's projects: param, or use scope:'all'. The working-directory project is marked (current).",
|
|
2270
|
+
inputSchema: {},
|
|
2271
|
+
_meta: { "anthropic/alwaysLoad": true },
|
|
2272
|
+
}, handleListProjects);
|
|
2273
|
+
tool("summarize_directory", {
|
|
2274
|
+
description: "Generate LLM summaries for indexed chunks.",
|
|
2275
|
+
inputSchema: {
|
|
2276
|
+
path: zod_1.z
|
|
2277
|
+
.string()
|
|
2278
|
+
.describe("Directory to summarize (default: project root)")
|
|
2279
|
+
.optional(),
|
|
2280
|
+
limit: zod_1.z
|
|
2281
|
+
.number()
|
|
2282
|
+
.describe("Max chunks (default 200, max 5000)")
|
|
2283
|
+
.optional(),
|
|
2284
|
+
},
|
|
2285
|
+
}, handleSummarizeDirectory);
|
|
2286
|
+
tool("summarize_project", {
|
|
2287
|
+
description: "Project overview: languages, structure, roles, key symbols, entry points.",
|
|
2288
|
+
inputSchema: {
|
|
2289
|
+
root: zod_1.z
|
|
2290
|
+
.string()
|
|
2291
|
+
.describe("Project root (default: current)")
|
|
2292
|
+
.optional(),
|
|
2293
|
+
},
|
|
2294
|
+
}, handleSummarizeProject);
|
|
2295
|
+
tool("related_files", {
|
|
2296
|
+
description: "Find dependencies and dependents of a file by shared symbols.",
|
|
2297
|
+
inputSchema: {
|
|
2298
|
+
file: zod_1.z.string().describe("File path relative to project root"),
|
|
2299
|
+
limit: zod_1.z
|
|
2300
|
+
.number()
|
|
2301
|
+
.describe("Max results per direction (default 10)")
|
|
2302
|
+
.optional(),
|
|
2303
|
+
},
|
|
2304
|
+
}, handleRelatedFiles);
|
|
2305
|
+
tool("recent_changes", {
|
|
2306
|
+
description: "Recently modified indexed files with timestamps.",
|
|
2307
|
+
inputSchema: {
|
|
2308
|
+
limit: zod_1.z
|
|
2309
|
+
.number()
|
|
2310
|
+
.describe("Max files to return (default 20)")
|
|
2311
|
+
.optional(),
|
|
2312
|
+
root: zod_1.z
|
|
2313
|
+
.string()
|
|
2314
|
+
.describe("Project root (defaults to current project)")
|
|
2315
|
+
.optional(),
|
|
2316
|
+
},
|
|
2317
|
+
}, handleRecentChanges);
|
|
2318
|
+
tool("diff_changes", {
|
|
2319
|
+
description: "Search code scoped to git changes. Omit ref for uncommitted changes.",
|
|
2320
|
+
inputSchema: {
|
|
2321
|
+
ref: zod_1.z
|
|
2322
|
+
.string()
|
|
2323
|
+
.describe("Git ref to diff against (e.g. main, HEAD~5)")
|
|
2324
|
+
.optional(),
|
|
2325
|
+
query: zod_1.z
|
|
2326
|
+
.string()
|
|
2327
|
+
.describe("Semantic search within changed files")
|
|
2328
|
+
.optional(),
|
|
2329
|
+
limit: zod_1.z.number().describe("Max results (default 10)").optional(),
|
|
2330
|
+
role: zod_1.z
|
|
2331
|
+
.string()
|
|
2332
|
+
.describe("Filter by role: ORCHESTRATION, DEFINITION, IMPLEMENTATION")
|
|
2333
|
+
.optional(),
|
|
2334
|
+
},
|
|
2335
|
+
}, handleDiffChanges);
|
|
2336
|
+
tool("find_tests", {
|
|
2337
|
+
description: "Find tests that exercise a symbol or file via reverse call graph.",
|
|
2338
|
+
inputSchema: {
|
|
2339
|
+
target: zod_1.z.string().describe("Symbol name or file path"),
|
|
2340
|
+
depth: zod_1.z
|
|
2341
|
+
.number()
|
|
2342
|
+
.describe("Caller traversal depth 1-3 (default 1)")
|
|
2343
|
+
.optional(),
|
|
2344
|
+
},
|
|
2345
|
+
}, handleFindTests);
|
|
2346
|
+
tool("impact_analysis", {
|
|
2347
|
+
description: "Change impact: dependents and affected tests for a symbol or file.",
|
|
2348
|
+
inputSchema: {
|
|
2349
|
+
target: zod_1.z.string().describe("Symbol name or file path"),
|
|
2350
|
+
depth: zod_1.z
|
|
2351
|
+
.number()
|
|
2352
|
+
.describe("Caller traversal depth 1-3 (default 1)")
|
|
2353
|
+
.optional(),
|
|
2354
|
+
},
|
|
2355
|
+
}, handleImpactAnalysis);
|
|
2356
|
+
tool("find_similar", {
|
|
2357
|
+
description: "Find semantically similar code using vector similarity.",
|
|
2358
|
+
inputSchema: {
|
|
2359
|
+
target: zod_1.z.string().describe("Symbol name or file path"),
|
|
2360
|
+
limit: zod_1.z.number().describe("Max results (default 5)").optional(),
|
|
2361
|
+
threshold: zod_1.z
|
|
2362
|
+
.number()
|
|
2363
|
+
.describe("Min similarity 0-1 (default 0)")
|
|
2364
|
+
.optional(),
|
|
2365
|
+
},
|
|
2366
|
+
}, handleFindSimilar);
|
|
2367
|
+
tool("build_context", {
|
|
2368
|
+
description: "Token-budgeted topic summary (search + skeleton + extract).",
|
|
2369
|
+
inputSchema: {
|
|
2370
|
+
topic: zod_1.z
|
|
2371
|
+
.string()
|
|
2372
|
+
.describe("Natural language topic or directory path"),
|
|
2373
|
+
budget: zod_1.z.number().describe("Max tokens (default 4000)").optional(),
|
|
2374
|
+
limit: zod_1.z
|
|
2375
|
+
.number()
|
|
2376
|
+
.describe("Search result limit (default 10)")
|
|
2377
|
+
.optional(),
|
|
2378
|
+
},
|
|
2379
|
+
}, handleBuildContext);
|
|
2380
|
+
tool("investigate", {
|
|
2381
|
+
description: "Agentic codebase Q&A: a local LLM answers questions using search, trace, peek, impact, and related tools. Requires LLM to be enabled (gmax llm on).",
|
|
2382
|
+
inputSchema: {
|
|
2383
|
+
question: zod_1.z
|
|
2384
|
+
.string()
|
|
2385
|
+
.describe("Natural language question about the codebase"),
|
|
2386
|
+
max_rounds: zod_1.z
|
|
2387
|
+
.number()
|
|
2388
|
+
.describe("Max tool-call rounds (default 10)")
|
|
2389
|
+
.optional(),
|
|
2390
|
+
},
|
|
2391
|
+
}, handleInvestigate);
|
|
2392
|
+
tool("review_commit", {
|
|
2393
|
+
description: "Review a git commit for bugs, breaking changes, and security issues using local LLM + codebase context. Returns structured findings. Requires LLM to be enabled (gmax llm on).",
|
|
2394
|
+
inputSchema: {
|
|
2395
|
+
commit: zod_1.z
|
|
2396
|
+
.string()
|
|
2397
|
+
.describe("Git ref to review (default: HEAD)")
|
|
2398
|
+
.optional(),
|
|
2399
|
+
},
|
|
2400
|
+
}, handleReviewCommit);
|
|
2401
|
+
tool("review_report", {
|
|
2402
|
+
description: "Get the accumulated code review report for the current project. Returns findings from all reviewed commits.",
|
|
2403
|
+
inputSchema: {
|
|
2404
|
+
json: zod_1.z
|
|
2405
|
+
.boolean()
|
|
2406
|
+
.describe("Return raw JSON instead of text (default: false)")
|
|
2407
|
+
.optional(),
|
|
2408
|
+
},
|
|
2409
|
+
}, handleReviewReport);
|
|
2410
|
+
tool("review_risk", {
|
|
2411
|
+
description: "Deterministic risk ranking of the symbols a commit's diff touches, ordered by blast radius (inbound callers) × test presence × file churn. No LLM required — fast, graph + git only. Use to triage what a change endangers before a deep review.",
|
|
2412
|
+
inputSchema: {
|
|
2413
|
+
commit: zod_1.z
|
|
2414
|
+
.string()
|
|
2415
|
+
.describe("Git ref to analyze (default: HEAD)")
|
|
2416
|
+
.optional(),
|
|
2417
|
+
},
|
|
2418
|
+
}, handleReviewRisk);
|
|
2547
2419
|
yield server.connect(transport);
|
|
2548
2420
|
// Kick off index readiness check and watcher in background
|
|
2549
2421
|
ensureIndexReady().catch((e) => console.error("[MCP] Index readiness check failed:", e));
|