grepmax 0.23.0 → 0.25.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 +1 -1
- package/README.md +25 -6
- package/dist/commands/add.js +38 -31
- package/dist/commands/config.js +16 -15
- package/dist/commands/context.js +38 -13
- package/dist/commands/doctor.js +76 -83
- package/dist/commands/extract.js +12 -1
- package/dist/commands/impact.js +33 -1
- package/dist/commands/index.js +23 -24
- package/dist/commands/list.js +23 -14
- package/dist/commands/mcp.js +494 -162
- package/dist/commands/peek.js +16 -5
- package/dist/commands/repair.js +54 -120
- package/dist/commands/search-output.js +30 -9
- package/dist/commands/search-run.js +75 -47
- package/dist/commands/search-skeletons.js +28 -18
- package/dist/commands/search.js +45 -49
- package/dist/commands/serve.js +415 -373
- package/dist/commands/setup.js +2 -2
- package/dist/commands/similar.js +5 -5
- package/dist/commands/skeleton.js +67 -41
- package/dist/commands/status.js +5 -2
- package/dist/commands/summarize.js +6 -0
- package/dist/commands/surprises.js +150 -0
- package/dist/commands/watch.js +102 -38
- package/dist/config.js +3 -0
- package/dist/eval-surprising-connections.js +191 -0
- package/dist/index.js +2 -0
- package/dist/lib/analysis/surprising-connections.js +600 -0
- package/dist/lib/daemon/daemon.js +1101 -379
- package/dist/lib/daemon/ipc-handler.js +122 -13
- package/dist/lib/daemon/mlx-server-manager.js +268 -125
- package/dist/lib/daemon/process-manager.js +7 -4
- package/dist/lib/daemon/search-handler.js +23 -9
- package/dist/lib/daemon/watcher-manager.js +353 -110
- package/dist/lib/graph/impact-rollup.js +202 -0
- package/dist/lib/graph/impact.js +15 -1
- package/dist/lib/help/agent-cheatsheet.js +1 -0
- package/dist/lib/index/batch-processor.js +231 -67
- package/dist/lib/index/embedding-generation.js +109 -0
- package/dist/lib/index/embedding-status.js +23 -0
- package/dist/lib/index/file-policy.js +273 -0
- package/dist/lib/index/ignore-patterns.js +4 -0
- package/dist/lib/index/index-config.js +18 -4
- package/dist/lib/index/syncer.js +256 -79
- package/dist/lib/index/walker.js +66 -86
- package/dist/lib/index/watcher-batch.js +9 -0
- package/dist/lib/index/watcher.js +129 -3
- package/dist/lib/llm/server.js +6 -0
- package/dist/lib/search/searcher.js +30 -19
- package/dist/lib/skeleton/skeletonizer.js +118 -0
- package/dist/lib/skeleton/summary-formatter.js +8 -1
- package/dist/lib/store/store-lease.js +473 -0
- package/dist/lib/store/vector-db.js +302 -57
- package/dist/lib/utils/blocked-roots.js +63 -0
- package/dist/lib/utils/cross-project.js +7 -3
- package/dist/lib/utils/daemon-client.js +24 -1
- package/dist/lib/utils/daemon-launcher.js +38 -13
- package/dist/lib/utils/doctor-status.js +76 -0
- package/dist/lib/utils/file-utils.js +74 -4
- package/dist/lib/utils/keyed-mutex.js +101 -0
- package/dist/lib/utils/logger.js +57 -1
- package/dist/lib/utils/mlx-hf-cache.js +114 -0
- package/dist/lib/utils/operation-coordinator.js +146 -0
- package/dist/lib/utils/path-containment.js +106 -0
- package/dist/lib/utils/process.js +44 -0
- package/dist/lib/utils/project-registry.js +351 -3
- package/dist/lib/utils/scope-filter.js +3 -9
- package/dist/lib/utils/stale-hint.js +12 -19
- package/dist/lib/utils/watcher-launcher.js +5 -1
- package/dist/lib/utils/watcher-store.js +2 -2
- package/dist/lib/workers/colbert-math.js +15 -12
- package/dist/lib/workers/embeddings/colbert.js +3 -2
- package/dist/lib/workers/embeddings/granite.js +4 -3
- package/dist/lib/workers/embeddings/mlx-client.js +59 -16
- package/dist/lib/workers/orchestrator.js +39 -8
- package/dist/lib/workers/pool.js +150 -83
- package/dist/lib/workers/process-child.js +11 -2
- package/dist/lib/workers/serialized-handler.js +10 -0
- package/dist/lib/workers/worker.js +13 -4
- package/mlx-embed-server/server.py +21 -3
- package/mlx-embed-server/summarizer.py +8 -0
- package/package.json +6 -3
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/plugins/grepmax/hooks/start.js +3 -170
package/dist/commands/mcp.js
CHANGED
|
@@ -53,11 +53,18 @@ exports.mcp = void 0;
|
|
|
53
53
|
exports.toStringArray = toStringArray;
|
|
54
54
|
exports.ok = ok;
|
|
55
55
|
exports.err = err;
|
|
56
|
+
exports.isExplicitCrossProjectSearch = isExplicitCrossProjectSearch;
|
|
57
|
+
exports.resolveMcpProject = resolveMcpProject;
|
|
58
|
+
exports.containMcpTarget = containMcpTarget;
|
|
59
|
+
exports.mcpRootPrefix = mcpRootPrefix;
|
|
60
|
+
exports.resolveMcpSourceFile = resolveMcpSourceFile;
|
|
56
61
|
exports.searchResultPath = searchResultPath;
|
|
57
62
|
exports.searchResultStartLine = searchResultStartLine;
|
|
58
63
|
exports.searchResultEndLine = searchResultEndLine;
|
|
59
64
|
exports.filterMcpSearchResults = filterMcpSearchResults;
|
|
60
65
|
exports.formatMcpPointerSearchResults = formatMcpPointerSearchResults;
|
|
66
|
+
exports.formatMcpSurprisingConnections = formatMcpSurprisingConnections;
|
|
67
|
+
exports.mcpLogQuery = mcpLogQuery;
|
|
61
68
|
const fs = __importStar(require("node:fs"));
|
|
62
69
|
const path = __importStar(require("node:path"));
|
|
63
70
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
@@ -65,8 +72,10 @@ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
|
65
72
|
const commander_1 = require("commander");
|
|
66
73
|
const zod_1 = require("zod");
|
|
67
74
|
const config_1 = require("../config");
|
|
75
|
+
const surprising_connections_1 = require("../lib/analysis/surprising-connections");
|
|
68
76
|
const languages_1 = require("../lib/core/languages");
|
|
69
77
|
const graph_builder_1 = require("../lib/graph/graph-builder");
|
|
78
|
+
const embedding_status_1 = require("../lib/index/embedding-status");
|
|
70
79
|
const index_config_1 = require("../lib/index/index-config");
|
|
71
80
|
const syncer_1 = require("../lib/index/syncer");
|
|
72
81
|
const agent_search_formatter_1 = require("../lib/output/agent-search-formatter");
|
|
@@ -77,12 +86,15 @@ const skeletonizer_1 = require("../lib/skeleton/skeletonizer");
|
|
|
77
86
|
const symbol_extractor_1 = require("../lib/skeleton/symbol-extractor");
|
|
78
87
|
const meta_cache_1 = require("../lib/store/meta-cache");
|
|
79
88
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
89
|
+
const cross_project_1 = require("../lib/utils/cross-project");
|
|
80
90
|
const file_utils_1 = require("../lib/utils/file-utils");
|
|
81
91
|
const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
82
92
|
const format_helpers_1 = require("../lib/utils/format-helpers");
|
|
83
93
|
const import_extractor_1 = require("../lib/utils/import-extractor");
|
|
94
|
+
const path_containment_1 = require("../lib/utils/path-containment");
|
|
84
95
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
85
96
|
const project_root_1 = require("../lib/utils/project-root");
|
|
97
|
+
const scope_filter_1 = require("../lib/utils/scope-filter");
|
|
86
98
|
const watcher_launcher_1 = require("../lib/utils/watcher-launcher");
|
|
87
99
|
const watcher_store_1 = require("../lib/utils/watcher-store");
|
|
88
100
|
const audit_1 = require("./audit");
|
|
@@ -109,6 +121,51 @@ function ok(text) {
|
|
|
109
121
|
function err(text) {
|
|
110
122
|
return { content: [{ type: "text", text }], isError: true };
|
|
111
123
|
}
|
|
124
|
+
function isExplicitCrossProjectSearch(args, isSearchAll = false) {
|
|
125
|
+
return (isSearchAll ||
|
|
126
|
+
args.scope === "all" ||
|
|
127
|
+
(typeof args.projects === "string" && args.projects.trim() !== "") ||
|
|
128
|
+
(typeof args.exclude_projects === "string" &&
|
|
129
|
+
args.exclude_projects.trim() !== ""));
|
|
130
|
+
}
|
|
131
|
+
function resolveMcpProject(requested, currentRoot, projects = (0, project_registry_1.listProjects)()) {
|
|
132
|
+
if (typeof requested === "string" && requested.trim()) {
|
|
133
|
+
const value = requested.trim();
|
|
134
|
+
const byName = projects.filter((project) => project.name === value);
|
|
135
|
+
if (byName.length === 1)
|
|
136
|
+
return byName[0];
|
|
137
|
+
if (byName.length > 1)
|
|
138
|
+
return undefined;
|
|
139
|
+
const resolved = path.resolve(value);
|
|
140
|
+
return projects.find((project) => path.resolve(project.root) === resolved);
|
|
141
|
+
}
|
|
142
|
+
const resolvedCurrent = path.resolve(currentRoot);
|
|
143
|
+
const exact = projects.find((project) => path.resolve(project.root) === resolvedCurrent);
|
|
144
|
+
if (exact)
|
|
145
|
+
return exact;
|
|
146
|
+
return projects
|
|
147
|
+
.filter((project) => (0, path_containment_1.isPathWithin)(project.root, resolvedCurrent))
|
|
148
|
+
.sort((a, b) => b.root.length - a.root.length)[0];
|
|
149
|
+
}
|
|
150
|
+
function containMcpTarget(root, target) {
|
|
151
|
+
return path.isAbsolute(target) ||
|
|
152
|
+
target.includes("/") ||
|
|
153
|
+
target.includes("\\")
|
|
154
|
+
? (0, path_containment_1.resolveContainedPath)(root, target)
|
|
155
|
+
: target;
|
|
156
|
+
}
|
|
157
|
+
function mcpRootPrefix(root) {
|
|
158
|
+
return root.endsWith(path.sep) ? root : `${root}${path.sep}`;
|
|
159
|
+
}
|
|
160
|
+
function resolveMcpSourceFile(roots, candidate) {
|
|
161
|
+
for (const root of roots) {
|
|
162
|
+
try {
|
|
163
|
+
return (0, path_containment_1.resolveContainedFile)(root, candidate);
|
|
164
|
+
}
|
|
165
|
+
catch (_a) { }
|
|
166
|
+
}
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
112
169
|
function chunkAbsPath(chunk) {
|
|
113
170
|
const metadata = chunk.metadata;
|
|
114
171
|
return String(chunk.path || (metadata === null || metadata === void 0 ? void 0 : metadata.path) || "");
|
|
@@ -171,6 +228,59 @@ function formatMcpPointerSearchResults(data, displayRoot, options = {}) {
|
|
|
171
228
|
query: options.query,
|
|
172
229
|
});
|
|
173
230
|
}
|
|
231
|
+
function formatMcpSurprisingConnections(result, top = 10) {
|
|
232
|
+
const { summary, findings } = result;
|
|
233
|
+
const lines = [
|
|
234
|
+
[
|
|
235
|
+
"summary",
|
|
236
|
+
`sampled=${summary.sampledAnchors}`,
|
|
237
|
+
`code=${summary.codeRows}`,
|
|
238
|
+
`pairs=${summary.acceptedPairs}`,
|
|
239
|
+
`file_pairs=${summary.acceptedFilePairs}`,
|
|
240
|
+
`score_p90=${summary.actionabilityScore.p90}`,
|
|
241
|
+
].join("\t"),
|
|
242
|
+
];
|
|
243
|
+
for (const finding of findings.slice(0, top)) {
|
|
244
|
+
const pair = finding.representative;
|
|
245
|
+
lines.push([
|
|
246
|
+
"surprise",
|
|
247
|
+
finding.score.toFixed(3),
|
|
248
|
+
finding.maxSimilarity.toFixed(3),
|
|
249
|
+
String(finding.pairCount),
|
|
250
|
+
finding.fileA,
|
|
251
|
+
finding.fileB,
|
|
252
|
+
(0, surprising_connections_1.lineLabel)(pair.source),
|
|
253
|
+
(0, surprising_connections_1.lineLabel)(pair.target),
|
|
254
|
+
finding.reasons.join(","),
|
|
255
|
+
`buckets=${(0, surprising_connections_1.findingBucketLabel)(finding, summary.options.dirDepth)}`,
|
|
256
|
+
`top_sims=${finding.topSimilarities.join(",")}`,
|
|
257
|
+
`penalties=${(0, surprising_connections_1.formatPenaltySummary)(pair.scoreParts)}`,
|
|
258
|
+
`next=${(0, surprising_connections_1.skeletonHint)(finding)}`,
|
|
259
|
+
].join("\t"));
|
|
260
|
+
}
|
|
261
|
+
if (findings.length === 0)
|
|
262
|
+
lines.push("none");
|
|
263
|
+
return lines.join("\n");
|
|
264
|
+
}
|
|
265
|
+
function mcpLogQuery(name, args) {
|
|
266
|
+
var _a, _b;
|
|
267
|
+
const direct = (_b = (_a = args.query) !== null && _a !== void 0 ? _a : args.symbol) !== null && _b !== void 0 ? _b : args.target;
|
|
268
|
+
if (direct !== undefined && direct !== null && String(direct) !== "") {
|
|
269
|
+
return String(direct);
|
|
270
|
+
}
|
|
271
|
+
if (name === "surprising_connections") {
|
|
272
|
+
const parts = [
|
|
273
|
+
"surprising_connections",
|
|
274
|
+
typeof args.root === "string" && args.root ? `root=${args.root}` : "",
|
|
275
|
+
typeof args.in === "string" && args.in ? `in=${args.in}` : "",
|
|
276
|
+
typeof args.exclude === "string" && args.exclude
|
|
277
|
+
? `exclude=${args.exclude}`
|
|
278
|
+
: "",
|
|
279
|
+
].filter(Boolean);
|
|
280
|
+
return parts.join(" ");
|
|
281
|
+
}
|
|
282
|
+
return "";
|
|
283
|
+
}
|
|
174
284
|
// ---------------------------------------------------------------------------
|
|
175
285
|
// Command
|
|
176
286
|
// ---------------------------------------------------------------------------
|
|
@@ -251,12 +361,16 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
251
361
|
});
|
|
252
362
|
}
|
|
253
363
|
// --- Background watcher ---
|
|
254
|
-
function ensureWatcher() {
|
|
364
|
+
function ensureWatcher(root) {
|
|
255
365
|
return __awaiter(this, void 0, void 0, function* () {
|
|
366
|
+
var _a;
|
|
256
367
|
try {
|
|
257
|
-
const
|
|
368
|
+
const watchRoot = root !== null && root !== void 0 ? root : (_a = resolveMcpProject(undefined, projectRoot)) === null || _a === void 0 ? void 0 : _a.root;
|
|
369
|
+
if (!watchRoot)
|
|
370
|
+
return;
|
|
371
|
+
const result = yield (0, watcher_launcher_1.launchWatcher)(watchRoot);
|
|
258
372
|
if (result.ok && !result.reused) {
|
|
259
|
-
console.log(`[MCP] Started background watcher for ${
|
|
373
|
+
console.log(`[MCP] Started background watcher for ${watchRoot} (PID: ${result.pid})`);
|
|
260
374
|
}
|
|
261
375
|
}
|
|
262
376
|
catch (err) {
|
|
@@ -264,66 +378,78 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
264
378
|
}
|
|
265
379
|
});
|
|
266
380
|
}
|
|
381
|
+
// Resolve the registered project this server scopes to. The server pins to
|
|
382
|
+
// its launch cwd (findProjectRoot, .git-only), but a registered project may
|
|
383
|
+
// be an umbrella with no .git of its own — so a session launched in a
|
|
384
|
+
// subdirectory resolves to a path that isn't the registered root. Prefer an
|
|
385
|
+
// exact registry match; otherwise walk the registry for an ancestor that
|
|
386
|
+
// covers the cwd, and scope to it instead of silently widening to the whole
|
|
387
|
+
// index. `root` is the registered project root to scope to.
|
|
388
|
+
function resolveRegisteredProject(requested) {
|
|
389
|
+
const project = resolveMcpProject(requested, projectRoot);
|
|
390
|
+
if (project)
|
|
391
|
+
return { proj: project, root: project.root };
|
|
392
|
+
return { proj: undefined, root: projectRoot };
|
|
393
|
+
}
|
|
267
394
|
// --- Tool handlers ---
|
|
268
395
|
function handleSemanticSearch(args_1) {
|
|
269
396
|
return __awaiter(this, arguments, void 0, function* (args, isSearchAll = false) {
|
|
397
|
+
var _a, _b;
|
|
270
398
|
const query = String(args.query || "");
|
|
271
399
|
if (!query)
|
|
272
400
|
return err("Missing required parameter: query");
|
|
273
|
-
|
|
401
|
+
const searchAll = isExplicitCrossProjectSearch(args, isSearchAll);
|
|
274
402
|
const limit = Math.min(Math.max(Number(args.limit) || 3, 1), 50);
|
|
275
|
-
ensureWatcher();
|
|
276
403
|
// Project resolution. The server is pinned to whatever cwd it launched in
|
|
277
|
-
// (resolved once at startup).
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
404
|
+
// (resolved once at startup). Resolve that cwd to its registered project —
|
|
405
|
+
// exact match, or a registered ancestor when the cwd is a subdirectory of
|
|
406
|
+
// an umbrella project. Searches scope to `resolvedRoot`.
|
|
407
|
+
const { proj, root: resolvedRoot } = resolveRegisteredProject(args.root);
|
|
408
|
+
if (searchAll &&
|
|
409
|
+
(args.root !== undefined ||
|
|
410
|
+
args.path !== undefined ||
|
|
411
|
+
args.exclude !== undefined)) {
|
|
412
|
+
return err("root, path, and exclude are single-project filters; omit them for cross-project search.");
|
|
413
|
+
}
|
|
414
|
+
// Cross-project search (the whole index) is opt-in: it happens only when
|
|
415
|
+
// the caller passes scope:"all" or projects:"…". Otherwise we require a
|
|
416
|
+
// resolved project and error loudly when there isn't one — never silently
|
|
417
|
+
// widen a scoped query to every indexed project.
|
|
418
|
+
if (!searchAll) {
|
|
419
|
+
if (!proj) {
|
|
420
|
+
if (typeof args.root === "string") {
|
|
421
|
+
return err("Project not added to gmax yet. Run `gmax add` to index it first.");
|
|
422
|
+
}
|
|
423
|
+
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; });
|
|
424
|
+
if (indexed.length === 0) {
|
|
425
|
+
return err("No indexed projects yet. cd into a repo and run `gmax add` to index it first.");
|
|
426
|
+
}
|
|
427
|
+
return err(`${path.basename(projectRoot)}/ isn't an indexed gmax project. ` +
|
|
428
|
+
`Pass scope:"all" to search all ${indexed.length} indexed project(s), ` +
|
|
429
|
+
`projects:"name" to target specific ones, or cd into an indexed project.`);
|
|
430
|
+
}
|
|
431
|
+
if (proj.status === "pending" || proj.chunkCount === 0) {
|
|
432
|
+
return err("Project not indexed yet. Run `gmax add` to index it first.");
|
|
433
|
+
}
|
|
434
|
+
void ensureWatcher(resolvedRoot);
|
|
299
435
|
}
|
|
300
436
|
try {
|
|
301
|
-
const searcher = getSearcher();
|
|
302
437
|
// Determine path prefix and display root for relative paths
|
|
303
438
|
let pathPrefix;
|
|
304
|
-
|
|
439
|
+
const displayRoot = resolvedRoot;
|
|
440
|
+
let projectScope;
|
|
305
441
|
if (!searchAll) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
: path.
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
displayRoot = searchRoot;
|
|
313
|
-
pathPrefix = searchRoot.endsWith("/") ? searchRoot : `${searchRoot}/`;
|
|
314
|
-
if (typeof args.path === "string") {
|
|
315
|
-
pathPrefix = path.join(searchRoot, args.path);
|
|
316
|
-
if (!pathPrefix.endsWith("/"))
|
|
317
|
-
pathPrefix += "/";
|
|
318
|
-
}
|
|
442
|
+
projectScope = (0, scope_filter_1.resolveScope)({
|
|
443
|
+
projectRoot: resolvedRoot,
|
|
444
|
+
in: typeof args.path === "string" ? args.path : undefined,
|
|
445
|
+
exclude: typeof args.exclude === "string" ? args.exclude : undefined,
|
|
446
|
+
});
|
|
447
|
+
pathPrefix = projectScope.pathPrefix;
|
|
319
448
|
}
|
|
320
449
|
const filters = {};
|
|
321
450
|
if (typeof args.file === "string" && args.file) {
|
|
322
451
|
filters.file = args.file;
|
|
323
452
|
}
|
|
324
|
-
if (typeof args.exclude === "string" && args.exclude) {
|
|
325
|
-
filters.exclude = args.exclude;
|
|
326
|
-
}
|
|
327
453
|
if (typeof args.language === "string" && args.language) {
|
|
328
454
|
filters.language = args.language;
|
|
329
455
|
}
|
|
@@ -331,29 +457,32 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
331
457
|
filters.role = args.role;
|
|
332
458
|
}
|
|
333
459
|
if (searchAll) {
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
.
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
460
|
+
const crossScope = (0, cross_project_1.resolveCrossProjectScope)({
|
|
461
|
+
allProjects: searchAll &&
|
|
462
|
+
!(typeof args.projects === "string" && args.projects.trim()),
|
|
463
|
+
projects: typeof args.projects === "string" ? args.projects : undefined,
|
|
464
|
+
excludeProjects: typeof args.exclude_projects === "string"
|
|
465
|
+
? args.exclude_projects
|
|
466
|
+
: undefined,
|
|
467
|
+
});
|
|
468
|
+
if (crossScope.projectRoots.length === 0) {
|
|
469
|
+
return err("No matching indexed projects.");
|
|
343
470
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
if (roots.length > 0) {
|
|
353
|
-
filters.exclude_project_roots = roots.join(",");
|
|
354
|
-
}
|
|
471
|
+
filters.projectRoots = crossScope.projectRoots;
|
|
472
|
+
}
|
|
473
|
+
else if (projectScope) {
|
|
474
|
+
if (projectScope.inPrefixes.length > 0) {
|
|
475
|
+
filters.inPrefixes = projectScope.inPrefixes;
|
|
476
|
+
}
|
|
477
|
+
if (projectScope.excludePrefixes.length > 0) {
|
|
478
|
+
filters.excludePrefixes = projectScope.excludePrefixes;
|
|
355
479
|
}
|
|
356
480
|
}
|
|
481
|
+
const selectedRoots = searchAll
|
|
482
|
+
? new Set((_a = filters.projectRoots) !== null && _a !== void 0 ? _a : [])
|
|
483
|
+
: new Set([resolvedRoot]);
|
|
484
|
+
(0, embedding_status_1.assertEmbeddingSearchCompatible)((0, project_registry_1.listProjects)().filter((project) => selectedRoots.has(project.root)), (0, index_config_1.readGlobalConfig)());
|
|
485
|
+
const searcher = getSearcher();
|
|
357
486
|
// Aider-style seeding: the agent passes its open files / discussed
|
|
358
487
|
// symbols; the searcher biases candidate generation toward them.
|
|
359
488
|
const parseSeedList = (v) => {
|
|
@@ -372,11 +501,18 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
372
501
|
? { files: seedFiles, symbols: seedSymbols }
|
|
373
502
|
: undefined;
|
|
374
503
|
const result = yield searcher.search(query, limit, { rerank: process.env.GMAX_RERANK === "1", seeds }, Object.keys(filters).length > 0 ? filters : undefined, pathPrefix);
|
|
375
|
-
|
|
376
|
-
|
|
504
|
+
const allowedRoots = searchAll
|
|
505
|
+
? ((_b = filters.projectRoots) !== null && _b !== void 0 ? _b : [])
|
|
506
|
+
: [resolvedRoot];
|
|
507
|
+
result.data = result.data.filter((chunk) => {
|
|
508
|
+
const candidate = chunkAbsPath(chunk);
|
|
509
|
+
return (candidate.length > 0 &&
|
|
510
|
+
allowedRoots.some((root) => (0, path_containment_1.isPathWithin)(root, candidate)));
|
|
511
|
+
});
|
|
512
|
+
// Prepend any searcher warnings to whatever body we return.
|
|
377
513
|
const prefixNotes = (body) => {
|
|
378
514
|
var _a;
|
|
379
|
-
const notes =
|
|
515
|
+
const notes = ((_a = result.warnings) !== null && _a !== void 0 ? _a : []).filter(Boolean);
|
|
380
516
|
return notes.length ? `${notes.join("\n")}\n\n${body}` : body;
|
|
381
517
|
};
|
|
382
518
|
if (!result.data || result.data.length === 0) {
|
|
@@ -393,8 +529,11 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
393
529
|
var _a;
|
|
394
530
|
if (!includeImports || !absPath)
|
|
395
531
|
return "";
|
|
532
|
+
const sourcePath = resolveMcpSourceFile(allowedRoots, absPath);
|
|
533
|
+
if (!sourcePath)
|
|
534
|
+
return "";
|
|
396
535
|
if (!importCache.has(absPath)) {
|
|
397
|
-
importCache.set(absPath, (0, import_extractor_1.extractImports)(
|
|
536
|
+
importCache.set(absPath, (0, import_extractor_1.extractImports)(sourcePath));
|
|
398
537
|
}
|
|
399
538
|
return (_a = importCache.get(absPath)) !== null && _a !== void 0 ? _a : "";
|
|
400
539
|
};
|
|
@@ -438,7 +577,10 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
438
577
|
if (contextN > 0 && absPath) {
|
|
439
578
|
// Read surrounding context from file
|
|
440
579
|
try {
|
|
441
|
-
const
|
|
580
|
+
const sourcePath = resolveMcpSourceFile(allowedRoots, absPath);
|
|
581
|
+
if (!sourcePath)
|
|
582
|
+
throw new Error("indexed path is outside scope");
|
|
583
|
+
const fileContent = fs.readFileSync(sourcePath, "utf-8");
|
|
442
584
|
const fileLines = fileContent.split("\n");
|
|
443
585
|
const ctxStart = Math.max(0, startLine - contextN);
|
|
444
586
|
const ctxEnd = Math.min(fileLines.length, endLine + 1 + contextN);
|
|
@@ -503,7 +645,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
503
645
|
const regex = new RegExp(namePattern, "i");
|
|
504
646
|
results = results.filter((r) => r.symbols.some((s) => regex.test(s)));
|
|
505
647
|
}
|
|
506
|
-
catch (
|
|
648
|
+
catch (_c) {
|
|
507
649
|
// Invalid regex — skip filter
|
|
508
650
|
}
|
|
509
651
|
}
|
|
@@ -512,19 +654,19 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
512
654
|
if (mode === "symbol" && !searchAll) {
|
|
513
655
|
try {
|
|
514
656
|
const db = getVectorDb();
|
|
515
|
-
const builder = new graph_builder_1.GraphBuilder(db);
|
|
657
|
+
const builder = new graph_builder_1.GraphBuilder(db, resolvedRoot);
|
|
516
658
|
const graph = yield builder.buildGraph(query);
|
|
517
659
|
if (graph.center) {
|
|
518
660
|
const traceLines = ["", "--- Call graph ---"];
|
|
519
|
-
const centerRel = graph.center.file.startsWith(
|
|
520
|
-
? graph.center.file.slice(
|
|
661
|
+
const centerRel = graph.center.file.startsWith(resolvedRoot)
|
|
662
|
+
? graph.center.file.slice(resolvedRoot.length + 1)
|
|
521
663
|
: graph.center.file;
|
|
522
664
|
traceLines.push(`${graph.center.symbol} [${graph.center.role}] ${centerRel}:${graph.center.line + 1}`);
|
|
523
665
|
if (graph.callers.length > 0) {
|
|
524
666
|
traceLines.push("Callers:");
|
|
525
667
|
for (const caller of graph.callers) {
|
|
526
|
-
const rel = caller.file.startsWith(
|
|
527
|
-
? caller.file.slice(
|
|
668
|
+
const rel = caller.file.startsWith(resolvedRoot)
|
|
669
|
+
? caller.file.slice(resolvedRoot.length + 1)
|
|
528
670
|
: caller.file;
|
|
529
671
|
traceLines.push(` <- ${caller.symbol} ${rel}:${caller.line + 1}`);
|
|
530
672
|
}
|
|
@@ -533,8 +675,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
533
675
|
traceLines.push("Calls:");
|
|
534
676
|
for (const callee of graph.callees.slice(0, 15)) {
|
|
535
677
|
if (callee.file) {
|
|
536
|
-
const rel = callee.file.startsWith(
|
|
537
|
-
? callee.file.slice(
|
|
678
|
+
const rel = callee.file.startsWith(resolvedRoot)
|
|
679
|
+
? callee.file.slice(resolvedRoot.length + 1)
|
|
538
680
|
: callee.file;
|
|
539
681
|
traceLines.push(` -> ${callee.symbol} ${rel}:${callee.line + 1}`);
|
|
540
682
|
}
|
|
@@ -546,7 +688,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
546
688
|
output += `\n${traceLines.join("\n")}`;
|
|
547
689
|
}
|
|
548
690
|
}
|
|
549
|
-
catch (
|
|
691
|
+
catch (_d) {
|
|
550
692
|
// Trace failed — return search results without trace
|
|
551
693
|
}
|
|
552
694
|
}
|
|
@@ -560,10 +702,14 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
560
702
|
}
|
|
561
703
|
function handleCodeSkeleton(args) {
|
|
562
704
|
return __awaiter(this, void 0, void 0, function* () {
|
|
563
|
-
ensureWatcher();
|
|
564
705
|
const target = String(args.target || "");
|
|
565
706
|
if (!target)
|
|
566
707
|
return err("Missing required parameter: target");
|
|
708
|
+
const { proj, root } = resolveRegisteredProject();
|
|
709
|
+
if (!proj) {
|
|
710
|
+
return err("Project not added to gmax yet. Run `gmax add` to index it first.");
|
|
711
|
+
}
|
|
712
|
+
void ensureWatcher(root);
|
|
567
713
|
const fileLimit = Math.min(Math.max(Number(args.limit) || 10, 1), 20);
|
|
568
714
|
// Determine targets: comma-separated, directory, or single file
|
|
569
715
|
let targets;
|
|
@@ -574,12 +720,14 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
574
720
|
.filter(Boolean);
|
|
575
721
|
}
|
|
576
722
|
else {
|
|
577
|
-
const absPath =
|
|
723
|
+
const absPath = (0, path_containment_1.resolveContainedPath)(root, target, {
|
|
724
|
+
verifyExistingTarget: true,
|
|
725
|
+
});
|
|
578
726
|
if (fs.existsSync(absPath) && fs.statSync(absPath).isDirectory()) {
|
|
579
727
|
const entries = fs.readdirSync(absPath, { withFileTypes: true });
|
|
580
728
|
targets = entries
|
|
581
729
|
.filter((e) => e.isFile() && (0, file_utils_1.isIndexableFile)(path.join(absPath, e.name)))
|
|
582
|
-
.map((e) => path.relative(
|
|
730
|
+
.map((e) => path.relative(root, path.join(absPath, e.name)))
|
|
583
731
|
.slice(0, fileLimit);
|
|
584
732
|
if (targets.length === 0) {
|
|
585
733
|
return err(`No indexable files found in ${target}`);
|
|
@@ -595,8 +743,11 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
595
743
|
const jsonFiles = [];
|
|
596
744
|
const skel = yield getSkeletonizer();
|
|
597
745
|
for (const t of targets) {
|
|
598
|
-
|
|
599
|
-
|
|
746
|
+
let absPath;
|
|
747
|
+
try {
|
|
748
|
+
absPath = (0, path_containment_1.resolveContainedFile)(root, t);
|
|
749
|
+
}
|
|
750
|
+
catch (_a) {
|
|
600
751
|
if (fmt !== "json")
|
|
601
752
|
parts.push(`// ${t} — file not found`);
|
|
602
753
|
continue;
|
|
@@ -606,7 +757,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
606
757
|
try {
|
|
607
758
|
sourceContent = fs.readFileSync(absPath, "utf-8");
|
|
608
759
|
}
|
|
609
|
-
catch (
|
|
760
|
+
catch (_b) { }
|
|
610
761
|
let skeleton = "";
|
|
611
762
|
let language = "";
|
|
612
763
|
let tokenEstimate = 0;
|
|
@@ -619,7 +770,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
619
770
|
tokenEstimate = Math.ceil(cached.length / 4);
|
|
620
771
|
}
|
|
621
772
|
}
|
|
622
|
-
catch (
|
|
773
|
+
catch (_c) { }
|
|
623
774
|
// Generate live if no cache
|
|
624
775
|
if (!skeleton) {
|
|
625
776
|
try {
|
|
@@ -672,17 +823,17 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
672
823
|
}
|
|
673
824
|
function handleTraceCalls(args) {
|
|
674
825
|
return __awaiter(this, void 0, void 0, function* () {
|
|
675
|
-
ensureWatcher();
|
|
676
826
|
const symbol = String(args.symbol || "");
|
|
677
827
|
if (!symbol)
|
|
678
828
|
return err("Missing required parameter: symbol");
|
|
679
|
-
const proj = (
|
|
829
|
+
const { proj, root } = resolveRegisteredProject();
|
|
680
830
|
if (!proj) {
|
|
681
831
|
return err("Project not added to gmax yet. Run `gmax add` to index it first.");
|
|
682
832
|
}
|
|
833
|
+
void ensureWatcher(root);
|
|
683
834
|
try {
|
|
684
835
|
const db = getVectorDb();
|
|
685
|
-
const builder = new graph_builder_1.GraphBuilder(db);
|
|
836
|
+
const builder = new graph_builder_1.GraphBuilder(db, root);
|
|
686
837
|
const depth = Math.min(Math.max(Number(args.depth) || 1, 1), 3);
|
|
687
838
|
const graph = yield builder.buildGraphMultiHop(symbol, depth);
|
|
688
839
|
if (!graph.center) {
|
|
@@ -699,8 +850,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
699
850
|
if (filteredImporters.length > 0) {
|
|
700
851
|
lines.push("Imported by:");
|
|
701
852
|
for (const imp of filteredImporters.slice(0, 10)) {
|
|
702
|
-
const rel = imp.startsWith(
|
|
703
|
-
? imp.slice(
|
|
853
|
+
const rel = imp.startsWith(root)
|
|
854
|
+
? imp.slice(root.length + 1)
|
|
704
855
|
: imp;
|
|
705
856
|
lines.push(` ${rel}`);
|
|
706
857
|
}
|
|
@@ -709,8 +860,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
709
860
|
// Callers (recursive tree)
|
|
710
861
|
function formatCallerTree(trees, indent) {
|
|
711
862
|
for (const t of trees) {
|
|
712
|
-
const rel = t.node.file.startsWith(
|
|
713
|
-
? t.node.file.slice(
|
|
863
|
+
const rel = t.node.file.startsWith(root)
|
|
864
|
+
? t.node.file.slice(root.length + 1)
|
|
714
865
|
: t.node.file;
|
|
715
866
|
const pad = " ".repeat(indent);
|
|
716
867
|
lines.push(`${pad}<- ${t.node.symbol} ${rel}:${t.node.line + 1}`);
|
|
@@ -729,8 +880,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
729
880
|
lines.push("Calls:");
|
|
730
881
|
for (const callee of graph.callees.slice(0, 15)) {
|
|
731
882
|
if (callee.file) {
|
|
732
|
-
const rel = callee.file.startsWith(
|
|
733
|
-
? callee.file.slice(
|
|
883
|
+
const rel = callee.file.startsWith(root)
|
|
884
|
+
? callee.file.slice(root.length + 1)
|
|
734
885
|
: callee.file;
|
|
735
886
|
lines.push(` -> ${callee.symbol} ${rel}:${callee.line + 1}`);
|
|
736
887
|
}
|
|
@@ -760,7 +911,9 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
760
911
|
if (!symbol)
|
|
761
912
|
return err("Missing required parameter: symbol");
|
|
762
913
|
try {
|
|
763
|
-
const
|
|
914
|
+
const { proj, root } = resolveRegisteredProject(args.root);
|
|
915
|
+
if (!proj)
|
|
916
|
+
return err("Project not added to gmax yet.");
|
|
764
917
|
const db = getVectorDb();
|
|
765
918
|
const table = yield db.ensureTable();
|
|
766
919
|
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
@@ -800,7 +953,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
800
953
|
return ((ROLE_PRI[String(b.role)] || 0) - (ROLE_PRI[String(a.role)] || 0));
|
|
801
954
|
});
|
|
802
955
|
const best = sorted[0];
|
|
803
|
-
const filePath = String(best.path);
|
|
956
|
+
const filePath = (0, path_containment_1.resolveContainedFile)(root, String(best.path));
|
|
804
957
|
const startLine = Number(best.start_line || 0);
|
|
805
958
|
const endLine = Number(best.end_line || 0);
|
|
806
959
|
const role = String(best.role || "IMPLEMENTATION");
|
|
@@ -851,7 +1004,9 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
851
1004
|
if (!symbol)
|
|
852
1005
|
return err("Missing required parameter: symbol");
|
|
853
1006
|
try {
|
|
854
|
-
const
|
|
1007
|
+
const { proj, root } = resolveRegisteredProject(args.root);
|
|
1008
|
+
if (!proj)
|
|
1009
|
+
return err("Project not added to gmax yet.");
|
|
855
1010
|
const depth = Math.min(Math.max(Number(args.depth || 1), 1), 3);
|
|
856
1011
|
const db = getVectorDb();
|
|
857
1012
|
const { GraphBuilder } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/graph-builder")));
|
|
@@ -883,7 +1038,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
883
1038
|
let sigText = "(source not available)";
|
|
884
1039
|
let bodyLines = 0;
|
|
885
1040
|
try {
|
|
886
|
-
const
|
|
1041
|
+
const centerPath = (0, path_containment_1.resolveContainedFile)(root, center.file);
|
|
1042
|
+
const content = fs.readFileSync(centerPath, "utf-8");
|
|
887
1043
|
const lines = content.split("\n");
|
|
888
1044
|
const chunk = lines.slice(startLine, endLine + 1);
|
|
889
1045
|
bodyLines = chunk.length;
|
|
@@ -982,7 +1138,9 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
982
1138
|
if (!symbol)
|
|
983
1139
|
return err("Missing required parameter: symbol");
|
|
984
1140
|
try {
|
|
985
|
-
const
|
|
1141
|
+
const { proj, root } = resolveRegisteredProject(args.root);
|
|
1142
|
+
if (!proj)
|
|
1143
|
+
return err("Project not added to gmax yet.");
|
|
986
1144
|
const db = getVectorDb();
|
|
987
1145
|
const table = yield db.ensureTable();
|
|
988
1146
|
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
@@ -1027,9 +1185,9 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1027
1185
|
function handleAudit(args) {
|
|
1028
1186
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1029
1187
|
ensureWatcher();
|
|
1030
|
-
const
|
|
1031
|
-
|
|
1032
|
-
|
|
1188
|
+
const { proj, root } = resolveRegisteredProject(args.root);
|
|
1189
|
+
if (!proj)
|
|
1190
|
+
return err("Project not added to gmax yet.");
|
|
1033
1191
|
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
1034
1192
|
const top = Math.min(Math.max(Number(args.top) || 10, 1), 50);
|
|
1035
1193
|
try {
|
|
@@ -1106,6 +1264,43 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1106
1264
|
}
|
|
1107
1265
|
});
|
|
1108
1266
|
}
|
|
1267
|
+
function handleSurprisingConnections(args) {
|
|
1268
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1269
|
+
ensureWatcher();
|
|
1270
|
+
if (args.experimental !== true) {
|
|
1271
|
+
return err("surprising_connections is experimental; pass experimental:true.");
|
|
1272
|
+
}
|
|
1273
|
+
const { proj, root } = resolveRegisteredProject(args.root);
|
|
1274
|
+
if (!proj)
|
|
1275
|
+
return err("Project not added to gmax yet.");
|
|
1276
|
+
const top = Math.min(Math.max(Number(args.top) || 10, 1), 100);
|
|
1277
|
+
const sample = Math.min(Math.max(Number(args.sample) || surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.sample, 1), 10000);
|
|
1278
|
+
const neighbors = Math.min(Math.max(Number(args.neighbors) || surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.neighbors, 1), 200);
|
|
1279
|
+
const dirDepth = Math.min(Math.max(Number(args.dir_depth) || surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.dirDepth, 1), 20);
|
|
1280
|
+
const minSimilarity = Math.max(Number(args.min_similarity) || surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.minSimilarity, 0);
|
|
1281
|
+
const maxRows = Math.min(Math.max(Number(args.max_rows) || surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.maxRows, 1), surprising_connections_1.MAX_SURPRISE_ROWS);
|
|
1282
|
+
try {
|
|
1283
|
+
const db = getVectorDb();
|
|
1284
|
+
const table = yield db.ensureTable();
|
|
1285
|
+
const result = yield (0, surprising_connections_1.analyzeSurprisingConnections)(table, root, {
|
|
1286
|
+
sample,
|
|
1287
|
+
neighbors,
|
|
1288
|
+
dirDepth,
|
|
1289
|
+
minSimilarity,
|
|
1290
|
+
maxRows,
|
|
1291
|
+
includeTests: Boolean(args.include_tests),
|
|
1292
|
+
includeEval: Boolean(args.include_eval),
|
|
1293
|
+
in: typeof args.in === "string" ? args.in : undefined,
|
|
1294
|
+
exclude: typeof args.exclude === "string" ? args.exclude : undefined,
|
|
1295
|
+
});
|
|
1296
|
+
return ok(formatMcpSurprisingConnections(result, top));
|
|
1297
|
+
}
|
|
1298
|
+
catch (e) {
|
|
1299
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1300
|
+
return err(`Surprising connections failed: ${msg}`);
|
|
1301
|
+
}
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1109
1304
|
function handleGetNeighbors(args) {
|
|
1110
1305
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1111
1306
|
ensureWatcher();
|
|
@@ -1115,7 +1310,9 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1115
1310
|
const direction = args.direction === "callers" ? "callers" : "callees";
|
|
1116
1311
|
const maxHops = Math.min(Math.max(Number(args.max_hops) || 2, 1), 5);
|
|
1117
1312
|
try {
|
|
1118
|
-
const
|
|
1313
|
+
const { proj, root } = resolveRegisteredProject(args.root);
|
|
1314
|
+
if (!proj)
|
|
1315
|
+
return err("Project not added to gmax yet.");
|
|
1119
1316
|
const builder = new graph_builder_1.GraphBuilder(getVectorDb(), root);
|
|
1120
1317
|
const hits = yield builder.getNeighbors(symbol, direction, maxHops);
|
|
1121
1318
|
if (hits.length === 0) {
|
|
@@ -1150,7 +1347,9 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1150
1347
|
const direction = args.direction === "callers" ? "callers" : "callees";
|
|
1151
1348
|
const maxHops = Math.min(Math.max(Number(args.max_hops) || 6, 1), 10);
|
|
1152
1349
|
try {
|
|
1153
|
-
const
|
|
1350
|
+
const { proj, root } = resolveRegisteredProject(args.root);
|
|
1351
|
+
if (!proj)
|
|
1352
|
+
return err("Project not added to gmax yet.");
|
|
1154
1353
|
const builder = new graph_builder_1.GraphBuilder(getVectorDb(), root);
|
|
1155
1354
|
const pathSyms = yield builder.findPaths(from, to, direction, maxHops);
|
|
1156
1355
|
if (!pathSyms) {
|
|
@@ -1176,8 +1375,10 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1176
1375
|
return err("Missing required parameter: files (non-empty array)");
|
|
1177
1376
|
}
|
|
1178
1377
|
try {
|
|
1179
|
-
const
|
|
1180
|
-
|
|
1378
|
+
const { proj, root } = resolveRegisteredProject(args.root);
|
|
1379
|
+
if (!proj)
|
|
1380
|
+
return err("Project not added to gmax yet.");
|
|
1381
|
+
const abs = filesIn.map((f) => (0, path_containment_1.resolveContainedPath)(root, f, { verifyExistingTarget: true }));
|
|
1181
1382
|
const builder = new graph_builder_1.GraphBuilder(getVectorDb(), root);
|
|
1182
1383
|
const sg = yield builder.subgraphForFiles(abs);
|
|
1183
1384
|
if (sg.symbols.length === 0) {
|
|
@@ -1218,14 +1419,20 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1218
1419
|
const pattern = typeof args.pattern === "string" ? args.pattern : undefined;
|
|
1219
1420
|
const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 100);
|
|
1220
1421
|
const pathPrefix = typeof args.path === "string" ? args.path : undefined;
|
|
1221
|
-
const proj = (
|
|
1422
|
+
const { proj, root } = resolveRegisteredProject();
|
|
1222
1423
|
if (!proj) {
|
|
1223
1424
|
return err("Project not added to gmax yet. Run `gmax add` to index it first.");
|
|
1224
1425
|
}
|
|
1225
1426
|
try {
|
|
1226
1427
|
const db = getVectorDb();
|
|
1227
1428
|
const table = yield db.ensureTable();
|
|
1228
|
-
|
|
1429
|
+
const rootPrefix = root.endsWith("/") ? root : `${root}/`;
|
|
1430
|
+
let where = `array_length(defined_symbols) > 0 AND ${(0, filter_builder_1.pathStartsWith)(rootPrefix)}`;
|
|
1431
|
+
if (pathPrefix) {
|
|
1432
|
+
const absPrefix = (0, path_containment_1.resolveContainedPath)(root, pathPrefix);
|
|
1433
|
+
where += ` AND ${(0, filter_builder_1.pathStartsWith)((0, filter_builder_1.normalizePath)(absPrefix))}`;
|
|
1434
|
+
}
|
|
1435
|
+
const query = table
|
|
1229
1436
|
.query()
|
|
1230
1437
|
.select([
|
|
1231
1438
|
"defined_symbols",
|
|
@@ -1234,15 +1441,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1234
1441
|
"role",
|
|
1235
1442
|
"is_exported",
|
|
1236
1443
|
])
|
|
1237
|
-
.where(
|
|
1444
|
+
.where(where)
|
|
1238
1445
|
.limit(pattern ? 10000 : Math.max(limit * 50, 2000));
|
|
1239
|
-
if (pathPrefix) {
|
|
1240
|
-
// Support both absolute and relative path prefixes
|
|
1241
|
-
const absPrefix = path.isAbsolute(pathPrefix)
|
|
1242
|
-
? pathPrefix
|
|
1243
|
-
: path.resolve(projectRoot, pathPrefix);
|
|
1244
|
-
query = query.where((0, filter_builder_1.pathStartsWith)((0, filter_builder_1.normalizePath)(absPrefix)));
|
|
1245
|
-
}
|
|
1246
1446
|
const rows = yield query.toArray();
|
|
1247
1447
|
const map = new Map();
|
|
1248
1448
|
for (const row of rows) {
|
|
@@ -1282,8 +1482,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1282
1482
|
return ok("No symbols found. Run `gmax status` to verify the project is indexed, or `gmax index` to rebuild.");
|
|
1283
1483
|
}
|
|
1284
1484
|
const lines = entries.map((e) => {
|
|
1285
|
-
const rel = e.path.startsWith(
|
|
1286
|
-
? e.path.slice(
|
|
1485
|
+
const rel = e.path.startsWith(root)
|
|
1486
|
+
? e.path.slice(root.length + 1)
|
|
1287
1487
|
: e.path;
|
|
1288
1488
|
const roleTag = e.role ? ` [${e.role.slice(0, 4)}]` : "";
|
|
1289
1489
|
const expTag = e.exported ? " exported" : "";
|
|
@@ -1305,13 +1505,15 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1305
1505
|
if (projects.length === 0) {
|
|
1306
1506
|
return ok("No projects indexed yet. cd into a repo and run `gmax add` to index it.");
|
|
1307
1507
|
}
|
|
1308
|
-
const currentName = (_a = (
|
|
1508
|
+
const currentName = (_a = resolveMcpProject(undefined, projectRoot)) === null || _a === void 0 ? void 0 : _a.name;
|
|
1509
|
+
const globalConfig = (0, index_config_1.readGlobalConfig)();
|
|
1309
1510
|
const lines = projects.map((p) => {
|
|
1310
1511
|
const here = p.name === currentName ? " (current)" : "";
|
|
1311
1512
|
const chunks = typeof p.chunkCount === "number"
|
|
1312
1513
|
? `\t(${p.chunkCount} chunks)`
|
|
1313
1514
|
: "";
|
|
1314
|
-
|
|
1515
|
+
const identity = (0, embedding_status_1.projectEmbeddingStatus)(p, globalConfig);
|
|
1516
|
+
return `${p.name}${here}\t${p.root}\t${p.status}\tembedding=${identity.state}${chunks}`;
|
|
1315
1517
|
});
|
|
1316
1518
|
return ok(`${projects.length} indexed project(s). ` +
|
|
1317
1519
|
`Pass a name to semantic_search via projects:"name", or use scope:"all".\n\n` +
|
|
@@ -1325,11 +1527,12 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1325
1527
|
}
|
|
1326
1528
|
function handleIndexStatus() {
|
|
1327
1529
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1328
|
-
var _a
|
|
1530
|
+
var _a;
|
|
1329
1531
|
try {
|
|
1330
|
-
const config = (0, index_config_1.readIndexConfig)(config_1.PATHS.configPath);
|
|
1331
1532
|
const globalConfig = (0, index_config_1.readGlobalConfig)();
|
|
1332
1533
|
const projects = (0, project_registry_1.listProjects)();
|
|
1534
|
+
const currentProject = resolveMcpProject(undefined, projectRoot);
|
|
1535
|
+
const identity = (0, embedding_status_1.projectEmbeddingStatus)(currentProject, globalConfig);
|
|
1333
1536
|
const db = getVectorDb();
|
|
1334
1537
|
const stats = yield db.getStats();
|
|
1335
1538
|
const fileCount = yield db.getDistinctFileCount();
|
|
@@ -1352,8 +1555,13 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1352
1555
|
// focused on index health and avoid the N-project LanceDB scans.
|
|
1353
1556
|
const lines = [
|
|
1354
1557
|
`Index: ~/.gmax/lancedb (${stats.chunks} chunks, ${fileCount} files)`,
|
|
1355
|
-
`
|
|
1356
|
-
|
|
1558
|
+
`Configured embedding: ${identity.configured.tier} ${identity.configured.vectorDim}d [${(0, embedding_status_1.embeddingFingerprintLabel)(identity.configured.fingerprint)}] (${globalConfig.embedMode})`,
|
|
1559
|
+
identity.built
|
|
1560
|
+
? `Built embedding: ${identity.built.tier} ${identity.built.vectorDim}d [${(0, embedding_status_1.embeddingFingerprintLabel)(identity.built.fingerprint)}] (${identity.state})`
|
|
1561
|
+
: "Built embedding: unbuilt",
|
|
1562
|
+
(currentProject === null || currentProject === void 0 ? void 0 : currentProject.lastIndexed)
|
|
1563
|
+
? `Last indexed: ${currentProject.lastIndexed}`
|
|
1564
|
+
: "",
|
|
1357
1565
|
watcherLine,
|
|
1358
1566
|
`Projects: ${projects.length} indexed (call list_projects for names + per-project chunk counts)`,
|
|
1359
1567
|
].filter(Boolean);
|
|
@@ -1367,7 +1575,12 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1367
1575
|
}
|
|
1368
1576
|
function handleSummarizeDirectory(args) {
|
|
1369
1577
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1370
|
-
const
|
|
1578
|
+
const { proj, root } = resolveRegisteredProject();
|
|
1579
|
+
if (!proj)
|
|
1580
|
+
return err("Project not added to gmax yet.");
|
|
1581
|
+
const dir = typeof args.path === "string"
|
|
1582
|
+
? (0, path_containment_1.resolveContainedPath)(root, args.path)
|
|
1583
|
+
: root;
|
|
1371
1584
|
const prefix = dir.endsWith("/") ? dir : `${dir}/`;
|
|
1372
1585
|
const limit = Math.min(Math.max(Number(args.limit) || 200, 1), 5000);
|
|
1373
1586
|
try {
|
|
@@ -1392,7 +1605,9 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1392
1605
|
function handleSummarizeProject(args) {
|
|
1393
1606
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1394
1607
|
var _a;
|
|
1395
|
-
const
|
|
1608
|
+
const { proj, root } = resolveRegisteredProject(args.root);
|
|
1609
|
+
if (!proj)
|
|
1610
|
+
return err("Project not added to gmax yet.");
|
|
1396
1611
|
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
1397
1612
|
const projectName = path.basename(root);
|
|
1398
1613
|
try {
|
|
@@ -1514,7 +1729,13 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1514
1729
|
if (!file)
|
|
1515
1730
|
return err("Missing required parameter: file");
|
|
1516
1731
|
const limit = Math.min(Math.max(Number(args.limit) || 10, 1), 25);
|
|
1517
|
-
const
|
|
1732
|
+
const { proj, root } = resolveRegisteredProject();
|
|
1733
|
+
if (!proj)
|
|
1734
|
+
return err("Project not added to gmax yet.");
|
|
1735
|
+
const absPath = (0, path_containment_1.resolveContainedPath)(root, file, {
|
|
1736
|
+
verifyExistingTarget: true,
|
|
1737
|
+
});
|
|
1738
|
+
const rootPrefix = root.endsWith("/") ? root : `${root}/`;
|
|
1518
1739
|
try {
|
|
1519
1740
|
const db = getVectorDb();
|
|
1520
1741
|
const table = yield db.ensureTable();
|
|
@@ -1542,7 +1763,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1542
1763
|
const rows = yield table
|
|
1543
1764
|
.query()
|
|
1544
1765
|
.select(["path"])
|
|
1545
|
-
.where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(sym)}')`)
|
|
1766
|
+
.where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(sym)}') AND ${(0, filter_builder_1.pathStartsWith)(rootPrefix)}`)
|
|
1546
1767
|
.limit(3)
|
|
1547
1768
|
.toArray();
|
|
1548
1769
|
for (const row of rows) {
|
|
@@ -1558,7 +1779,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1558
1779
|
const rows = yield table
|
|
1559
1780
|
.query()
|
|
1560
1781
|
.select(["path"])
|
|
1561
|
-
.where(`array_contains(referenced_symbols, '${(0, filter_builder_1.escapeSqlString)(sym)}')`)
|
|
1782
|
+
.where(`array_contains(referenced_symbols, '${(0, filter_builder_1.escapeSqlString)(sym)}') AND ${(0, filter_builder_1.pathStartsWith)(rootPrefix)}`)
|
|
1562
1783
|
.limit(20)
|
|
1563
1784
|
.toArray();
|
|
1564
1785
|
for (const row of rows) {
|
|
@@ -1576,8 +1797,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1576
1797
|
if (topDeps.length > 0) {
|
|
1577
1798
|
lines.push("Dependencies (files this imports/calls):");
|
|
1578
1799
|
for (const [p, count] of topDeps) {
|
|
1579
|
-
const rel = p.startsWith(
|
|
1580
|
-
? p.slice(
|
|
1800
|
+
const rel = p.startsWith(rootPrefix)
|
|
1801
|
+
? p.slice(rootPrefix.length)
|
|
1581
1802
|
: p;
|
|
1582
1803
|
lines.push(` ${rel.padEnd(40)} (${count} shared symbol${count > 1 ? "s" : ""})`);
|
|
1583
1804
|
}
|
|
@@ -1592,8 +1813,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1592
1813
|
if (topRevs.length > 0) {
|
|
1593
1814
|
lines.push("Dependents (files that call this):");
|
|
1594
1815
|
for (const [p, count] of topRevs) {
|
|
1595
|
-
const rel = p.startsWith(
|
|
1596
|
-
? p.slice(
|
|
1816
|
+
const rel = p.startsWith(rootPrefix)
|
|
1817
|
+
? p.slice(rootPrefix.length)
|
|
1597
1818
|
: p;
|
|
1598
1819
|
lines.push(` ${rel.padEnd(40)} (${count} shared symbol${count > 1 ? "s" : ""})`);
|
|
1599
1820
|
}
|
|
@@ -1614,7 +1835,9 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1614
1835
|
var _a, e_1, _b, _c;
|
|
1615
1836
|
ensureWatcher();
|
|
1616
1837
|
const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 50);
|
|
1617
|
-
const
|
|
1838
|
+
const { proj, root } = resolveRegisteredProject(args.root);
|
|
1839
|
+
if (!proj)
|
|
1840
|
+
return err("Project not added to gmax yet.");
|
|
1618
1841
|
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
1619
1842
|
try {
|
|
1620
1843
|
const metaCache = new meta_cache_1.MetaCache(config_1.PATHS.lmdbPath);
|
|
@@ -1675,16 +1898,20 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1675
1898
|
const limit = Math.min(Math.max(Number(args.limit) || 10, 1), 50);
|
|
1676
1899
|
const role = typeof args.role === "string" ? args.role.toUpperCase() : undefined;
|
|
1677
1900
|
try {
|
|
1678
|
-
const
|
|
1901
|
+
const { proj, root } = resolveRegisteredProject();
|
|
1902
|
+
if (!proj)
|
|
1903
|
+
return err("Project not added to gmax yet.");
|
|
1904
|
+
(0, embedding_status_1.assertEmbeddingSearchCompatible)([proj], (0, index_config_1.readGlobalConfig)());
|
|
1905
|
+
const changedFiles = getChangedFiles(ref, root);
|
|
1679
1906
|
if (changedFiles.length === 0) {
|
|
1680
1907
|
return ok(ref
|
|
1681
1908
|
? `No changes found relative to ${ref}.`
|
|
1682
1909
|
: "No uncommitted changes found.");
|
|
1683
1910
|
}
|
|
1684
|
-
const rel = (p) => p.startsWith(`${
|
|
1911
|
+
const rel = (p) => p.startsWith(`${root}/`) ? p.slice(root.length + 1) : p;
|
|
1685
1912
|
if (query) {
|
|
1686
1913
|
const searcher = getSearcher();
|
|
1687
|
-
const response = yield searcher.search(query, limit, { rerank: process.env.GMAX_RERANK === "1" }, {},
|
|
1914
|
+
const response = yield searcher.search(query, limit, { rerank: process.env.GMAX_RERANK === "1" }, {}, root);
|
|
1688
1915
|
const changedSet = new Set(changedFiles);
|
|
1689
1916
|
// searcher.search() returns mapped chunks (path under metadata.path);
|
|
1690
1917
|
// changedFiles are absolute, so match on the resolved absolute path.
|
|
@@ -1732,15 +1959,19 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1732
1959
|
return err("Missing required parameter: target");
|
|
1733
1960
|
const depth = Math.min(Math.max(Number(args.depth) || 1, 1), 3);
|
|
1734
1961
|
try {
|
|
1962
|
+
const { proj, root } = resolveRegisteredProject();
|
|
1963
|
+
if (!proj)
|
|
1964
|
+
return err("Project not added to gmax yet.");
|
|
1965
|
+
const scopedTarget = containMcpTarget(root, target);
|
|
1735
1966
|
const { resolveTargetSymbols, findTests } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/impact")));
|
|
1736
1967
|
const db = getVectorDb();
|
|
1737
|
-
const { symbols, symbolFamilies } = yield resolveTargetSymbols(
|
|
1968
|
+
const { symbols, symbolFamilies } = yield resolveTargetSymbols(scopedTarget, db, mcpRootPrefix(root));
|
|
1738
1969
|
if (symbols.length === 0)
|
|
1739
1970
|
return ok(`No symbols found for: ${target}`);
|
|
1740
|
-
const tests = yield findTests(symbols, db,
|
|
1971
|
+
const tests = yield findTests(symbols, db, root, depth, undefined, symbolFamilies);
|
|
1741
1972
|
if (tests.length === 0)
|
|
1742
1973
|
return ok(`No tests found for ${target}.`);
|
|
1743
|
-
const rel = (p) => p.startsWith(`${
|
|
1974
|
+
const rel = (p) => p.startsWith(`${root}/`) ? p.slice(root.length + 1) : p;
|
|
1744
1975
|
const lines = tests.map((t) => {
|
|
1745
1976
|
const hop = t.hops === 0 ? "direct" : `${t.hops}-hop`;
|
|
1746
1977
|
return `${rel(t.file)}:${t.line + 1} ${t.symbol} (${hop})`;
|
|
@@ -1759,22 +1990,47 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1759
1990
|
if (!target)
|
|
1760
1991
|
return err("Missing required parameter: target");
|
|
1761
1992
|
const depth = Math.min(Math.max(Number(args.depth) || 1, 1), 3);
|
|
1993
|
+
const rollup = args.rollup === true;
|
|
1994
|
+
const top = Math.min(Math.max(Number(args.top) || 10, 1), 100);
|
|
1762
1995
|
try {
|
|
1763
|
-
const {
|
|
1996
|
+
const { proj, root } = resolveRegisteredProject();
|
|
1997
|
+
if (!proj)
|
|
1998
|
+
return err("Project not added to gmax yet.");
|
|
1999
|
+
const scopedTarget = containMcpTarget(root, target);
|
|
2000
|
+
const { resolveTargetSymbols, findTests, findDependents, findDependentsDetailed, isTestPath, } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/impact")));
|
|
2001
|
+
const { buildImpactRollup, formatImpactRollupAgent } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/impact-rollup")));
|
|
1764
2002
|
const db = getVectorDb();
|
|
1765
|
-
const { symbols, resolvedAsFile, symbolFamilies } = yield resolveTargetSymbols(
|
|
2003
|
+
const { symbols, resolvedAsFile, symbolFamilies } = yield resolveTargetSymbols(scopedTarget, db, root);
|
|
1766
2004
|
if (symbols.length === 0)
|
|
1767
2005
|
return ok(`No symbols found for: ${target}`);
|
|
1768
2006
|
const targetPath = resolvedAsFile
|
|
1769
|
-
?
|
|
2007
|
+
? (0, path_containment_1.resolveContainedPath)(root, scopedTarget)
|
|
1770
2008
|
: undefined;
|
|
1771
2009
|
const excludePaths = targetPath ? new Set([targetPath]) : undefined;
|
|
2010
|
+
const rollupLimit = Math.min(Math.max(top * 10, 100), 500);
|
|
1772
2011
|
const [dependents, tests] = yield Promise.all([
|
|
1773
|
-
|
|
1774
|
-
|
|
2012
|
+
rollup
|
|
2013
|
+
? findDependentsDetailed(symbols, db, root, excludePaths, rollupLimit, undefined, symbolFamilies)
|
|
2014
|
+
: findDependents(symbols, db, root, excludePaths, undefined, undefined, symbolFamilies),
|
|
2015
|
+
findTests(symbols, db, root, depth, undefined, symbolFamilies),
|
|
1775
2016
|
]);
|
|
2017
|
+
if (rollup) {
|
|
2018
|
+
const detailedDependents = dependents;
|
|
2019
|
+
const impactRollup = buildImpactRollup({
|
|
2020
|
+
targetSymbols: symbols,
|
|
2021
|
+
dependents: detailedDependents,
|
|
2022
|
+
tests,
|
|
2023
|
+
projectRoot: root,
|
|
2024
|
+
top,
|
|
2025
|
+
});
|
|
2026
|
+
return ok(formatImpactRollupAgent(impactRollup, {
|
|
2027
|
+
target,
|
|
2028
|
+
projectRoot: root,
|
|
2029
|
+
includeTests: true,
|
|
2030
|
+
}));
|
|
2031
|
+
}
|
|
1776
2032
|
const nonTestDeps = dependents.filter((d) => !isTestPath(d.file));
|
|
1777
|
-
const rel = (p) => p.startsWith(`${
|
|
2033
|
+
const rel = (p) => p.startsWith(`${root}/`) ? p.slice(root.length + 1) : p;
|
|
1778
2034
|
const sections = [`Impact analysis for ${target}:\n`];
|
|
1779
2035
|
if (nonTestDeps.length > 0) {
|
|
1780
2036
|
sections.push(`Dependents (${nonTestDeps.length}):`);
|
|
@@ -1811,13 +2067,17 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1811
2067
|
const limit = Math.min(Math.max(Number(args.limit) || 5, 1), 25);
|
|
1812
2068
|
const threshold = Number(args.threshold) || 0;
|
|
1813
2069
|
try {
|
|
2070
|
+
const { proj, root } = resolveRegisteredProject();
|
|
2071
|
+
if (!proj)
|
|
2072
|
+
return err("Project not added to gmax yet.");
|
|
2073
|
+
(0, embedding_status_1.assertEmbeddingSearchCompatible)([proj], (0, index_config_1.readGlobalConfig)());
|
|
1814
2074
|
const db = getVectorDb();
|
|
1815
2075
|
const table = yield db.ensureTable();
|
|
1816
2076
|
const isFile = target.includes("/") ||
|
|
1817
2077
|
(target.includes(".") && !target.includes(" "));
|
|
1818
2078
|
let sourceRows;
|
|
1819
2079
|
if (isFile) {
|
|
1820
|
-
const absPath =
|
|
2080
|
+
const absPath = (0, path_containment_1.resolveContainedPath)(root, target);
|
|
1821
2081
|
sourceRows = yield table
|
|
1822
2082
|
.query()
|
|
1823
2083
|
.select(["vector", "path", "start_line"])
|
|
@@ -1829,7 +2089,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1829
2089
|
sourceRows = yield table
|
|
1830
2090
|
.query()
|
|
1831
2091
|
.select(["vector", "path", "start_line"])
|
|
1832
|
-
.where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(target)}')`)
|
|
2092
|
+
.where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(target)}') AND ${(0, filter_builder_1.pathStartsWith)(`${root}/`)}`)
|
|
1833
2093
|
.limit(1)
|
|
1834
2094
|
.toArray();
|
|
1835
2095
|
}
|
|
@@ -1849,7 +2109,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1849
2109
|
"role",
|
|
1850
2110
|
"_distance",
|
|
1851
2111
|
])
|
|
1852
|
-
.where((0, filter_builder_1.pathStartsWith)(`${
|
|
2112
|
+
.where((0, filter_builder_1.pathStartsWith)(`${root}/`))
|
|
1853
2113
|
.limit(limit + 5)
|
|
1854
2114
|
.toArray();
|
|
1855
2115
|
let filtered = results.filter((r) => !(r.path === source.path && r.start_line === source.start_line));
|
|
@@ -1857,7 +2117,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1857
2117
|
filtered = filtered.filter((r) => { var _a; return 1 / (1 + ((_a = r._distance) !== null && _a !== void 0 ? _a : 0)) >= threshold; });
|
|
1858
2118
|
if (filtered.length === 0)
|
|
1859
2119
|
return ok(`No similar code found for ${target}.`);
|
|
1860
|
-
const rel = (p) => p.startsWith(`${
|
|
2120
|
+
const rel = (p) => p.startsWith(`${root}/`) ? p.slice(root.length + 1) : p;
|
|
1861
2121
|
const lines = filtered.slice(0, limit).map((r) => {
|
|
1862
2122
|
var _a, _b, _c, _d;
|
|
1863
2123
|
const sym = (_b = (_a = toStringArray(r.defined_symbols)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : "";
|
|
@@ -1880,11 +2140,15 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1880
2140
|
const budget = Math.max(Number(args.budget) || 4000, 500);
|
|
1881
2141
|
const limit = Math.min(Math.max(Number(args.limit) || 10, 1), 25);
|
|
1882
2142
|
try {
|
|
2143
|
+
const { proj, root } = resolveRegisteredProject();
|
|
2144
|
+
if (!proj)
|
|
2145
|
+
return err("Project not added to gmax yet.");
|
|
2146
|
+
(0, embedding_status_1.assertEmbeddingSearchCompatible)([proj], (0, index_config_1.readGlobalConfig)());
|
|
1883
2147
|
const searcher = getSearcher();
|
|
1884
|
-
const response = yield searcher.search(topic, limit, { rerank: process.env.GMAX_RERANK === "1" }, {},
|
|
2148
|
+
const response = yield searcher.search(topic, limit, { rerank: process.env.GMAX_RERANK === "1" }, {}, root);
|
|
1885
2149
|
if (response.data.length === 0)
|
|
1886
2150
|
return ok(`No results found for "${topic}".`);
|
|
1887
|
-
const rel = (p) => p.startsWith(`${
|
|
2151
|
+
const rel = (p) => p.startsWith(`${root}/`) ? p.slice(root.length + 1) : p;
|
|
1888
2152
|
const estTokens = (s) => Math.ceil(s.length / 4);
|
|
1889
2153
|
let tokensUsed = 0;
|
|
1890
2154
|
const sections = [];
|
|
@@ -1905,7 +2169,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1905
2169
|
const endLine = searchResultEndLine(r, startLine);
|
|
1906
2170
|
const sym = (_b = (_a = toStringArray(r.defined_symbols)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : "";
|
|
1907
2171
|
try {
|
|
1908
|
-
const
|
|
2172
|
+
const sourcePath = (0, path_containment_1.resolveContainedFile)(root, absP);
|
|
2173
|
+
const content = fs.readFileSync(sourcePath, "utf-8");
|
|
1909
2174
|
const body = content
|
|
1910
2175
|
.split("\n")
|
|
1911
2176
|
.slice(startLine, endLine + 1)
|
|
@@ -2045,15 +2310,16 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2045
2310
|
"If you are NOT inside a specific repo, or want a different one: call " +
|
|
2046
2311
|
'`list_projects` to see what\'s indexed, then pass `projects:"name"` (or ' +
|
|
2047
2312
|
'`scope:"all"`) to `semantic_search`. When the working directory isn\'t an ' +
|
|
2048
|
-
"indexed project, `semantic_search`
|
|
2049
|
-
"
|
|
2313
|
+
"indexed project, `semantic_search` errors instead of searching everything " +
|
|
2314
|
+
"implicitly. Prefer `semantic_search` (detail:'pointer') for discovery, then " +
|
|
2050
2315
|
"`extract_symbol`/`peek_symbol`/`code_skeleton` to read, and " +
|
|
2051
|
-
"`trace_calls`/`impact_analysis`/`find_tests` for call-graph questions."
|
|
2316
|
+
"`trace_calls`/`impact_analysis`/`find_tests` for call-graph questions. " +
|
|
2317
|
+
"Use `audit` and experimental `surprising_connections` for project orientation.",
|
|
2052
2318
|
});
|
|
2053
2319
|
// Best-effort query logging, applied uniformly to every tool exactly as the
|
|
2054
2320
|
// old single CallToolRequestSchema dispatch did before each return.
|
|
2055
2321
|
const logToolCall = (name, toolArgs, startMs, result) => __awaiter(void 0, void 0, void 0, function* () {
|
|
2056
|
-
var _a, _b, _c
|
|
2322
|
+
var _a, _b, _c;
|
|
2057
2323
|
try {
|
|
2058
2324
|
const { logQuery } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/query-log")));
|
|
2059
2325
|
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 : "";
|
|
@@ -2062,22 +2328,31 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2062
2328
|
ts: new Date().toISOString(),
|
|
2063
2329
|
source: "mcp",
|
|
2064
2330
|
tool: name,
|
|
2065
|
-
query:
|
|
2331
|
+
query: mcpLogQuery(name, toolArgs),
|
|
2066
2332
|
project: projectRoot,
|
|
2067
2333
|
results: resultLines,
|
|
2068
2334
|
ms: Date.now() - startMs,
|
|
2069
2335
|
error: result.isError ? text.slice(0, 200) : undefined,
|
|
2070
2336
|
});
|
|
2071
2337
|
}
|
|
2072
|
-
catch (
|
|
2338
|
+
catch (_d) { }
|
|
2073
2339
|
});
|
|
2074
2340
|
// Register a tool, wrapping its handler with timing + query logging. Zod raw
|
|
2075
2341
|
// shapes give us free input validation (the SDK rejects calls that violate
|
|
2076
2342
|
// the schema before the handler runs); handlers stay defensively coded.
|
|
2077
2343
|
function tool(name, config, handler) {
|
|
2078
2344
|
server.registerTool(name, config, (rawArgs) => __awaiter(this, void 0, void 0, function* () {
|
|
2079
|
-
|
|
2345
|
+
let args = (rawArgs !== null && rawArgs !== void 0 ? rawArgs : {});
|
|
2080
2346
|
const startMs = Date.now();
|
|
2347
|
+
if (typeof args.root === "string" && args.root.trim()) {
|
|
2348
|
+
const selected = resolveMcpProject(args.root, projectRoot);
|
|
2349
|
+
if (!selected) {
|
|
2350
|
+
const result = err(`Unknown registered project: ${args.root}. Use list_projects to select a project name or exact root.`);
|
|
2351
|
+
yield logToolCall(name, args, startMs, result);
|
|
2352
|
+
return result;
|
|
2353
|
+
}
|
|
2354
|
+
args = Object.assign(Object.assign({}, args), { root: selected.root });
|
|
2355
|
+
}
|
|
2081
2356
|
const result = yield handler(args);
|
|
2082
2357
|
yield logToolCall(name, args, startMs, result);
|
|
2083
2358
|
return result;
|
|
@@ -2095,7 +2370,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2095
2370
|
.optional(),
|
|
2096
2371
|
root: zod_1.z
|
|
2097
2372
|
.string()
|
|
2098
|
-
.describe("
|
|
2373
|
+
.describe("Registered project name or exact root")
|
|
2099
2374
|
.optional(),
|
|
2100
2375
|
path: zod_1.z
|
|
2101
2376
|
.string()
|
|
@@ -2229,6 +2504,55 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2229
2504
|
.optional(),
|
|
2230
2505
|
},
|
|
2231
2506
|
}, handleAudit);
|
|
2507
|
+
tool("surprising_connections", {
|
|
2508
|
+
description: "Experimental orientation signal: embedding-similar cross-directory file pairs that are not directly connected by the static symbol graph. Useful for spotting duplicate or parallel logic. Requires experimental:true.",
|
|
2509
|
+
inputSchema: {
|
|
2510
|
+
experimental: zod_1.z
|
|
2511
|
+
.boolean()
|
|
2512
|
+
.describe("Must be true to acknowledge experimental signal quality"),
|
|
2513
|
+
root: zod_1.z
|
|
2514
|
+
.string()
|
|
2515
|
+
.describe("Project root (default: current)")
|
|
2516
|
+
.optional(),
|
|
2517
|
+
sample: zod_1.z
|
|
2518
|
+
.number()
|
|
2519
|
+
.describe("Indexed code chunks to sample (default 160, max 10000)")
|
|
2520
|
+
.optional(),
|
|
2521
|
+
neighbors: zod_1.z
|
|
2522
|
+
.number()
|
|
2523
|
+
.describe("Nearest neighbors per sampled chunk (default 20, max 200)")
|
|
2524
|
+
.optional(),
|
|
2525
|
+
top: zod_1.z
|
|
2526
|
+
.number()
|
|
2527
|
+
.describe("Grouped findings to return (default 10, max 100)")
|
|
2528
|
+
.optional(),
|
|
2529
|
+
dir_depth: zod_1.z
|
|
2530
|
+
.number()
|
|
2531
|
+
.describe("Directory bucket depth considered unsurprising")
|
|
2532
|
+
.optional(),
|
|
2533
|
+
min_similarity: zod_1.z
|
|
2534
|
+
.number()
|
|
2535
|
+
.describe("Minimum similarity 0-1 (default 0)")
|
|
2536
|
+
.optional(),
|
|
2537
|
+
max_rows: zod_1.z
|
|
2538
|
+
.number()
|
|
2539
|
+
.describe(`Maximum indexed rows to scan (default 50000, max ${surprising_connections_1.MAX_SURPRISE_ROWS})`)
|
|
2540
|
+
.optional(),
|
|
2541
|
+
in: zod_1.z
|
|
2542
|
+
.string()
|
|
2543
|
+
.describe("Restrict to a sub-path of the project")
|
|
2544
|
+
.optional(),
|
|
2545
|
+
exclude: zod_1.z
|
|
2546
|
+
.string()
|
|
2547
|
+
.describe("Exclude a sub-path of the project")
|
|
2548
|
+
.optional(),
|
|
2549
|
+
include_tests: zod_1.z.boolean().describe("Include test files").optional(),
|
|
2550
|
+
include_eval: zod_1.z
|
|
2551
|
+
.boolean()
|
|
2552
|
+
.describe("Include eval/experiment/script files")
|
|
2553
|
+
.optional(),
|
|
2554
|
+
},
|
|
2555
|
+
}, handleSurprisingConnections);
|
|
2232
2556
|
tool("get_neighbors", {
|
|
2233
2557
|
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`.",
|
|
2234
2558
|
inputSchema: {
|
|
@@ -2371,6 +2695,14 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2371
2695
|
.number()
|
|
2372
2696
|
.describe("Caller traversal depth 1-3 (default 1)")
|
|
2373
2697
|
.optional(),
|
|
2698
|
+
rollup: zod_1.z
|
|
2699
|
+
.boolean()
|
|
2700
|
+
.describe("Return export/package rollup TSV rows")
|
|
2701
|
+
.optional(),
|
|
2702
|
+
top: zod_1.z
|
|
2703
|
+
.number()
|
|
2704
|
+
.describe("Max rows per rollup section (default 10)")
|
|
2705
|
+
.optional(),
|
|
2374
2706
|
},
|
|
2375
2707
|
}, handleImpactAnalysis);
|
|
2376
2708
|
tool("find_similar", {
|