grepmax 0.24.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 +11 -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/index.js +23 -24
- package/dist/commands/list.js +23 -14
- package/dist/commands/mcp.js +278 -143
- 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/watch.js +102 -38
- package/dist/config.js +3 -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/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/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 +146 -82
- 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 +4 -3
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/plugins/grepmax/hooks/start.js +3 -170
package/dist/commands/mcp.js
CHANGED
|
@@ -54,6 +54,10 @@ exports.toStringArray = toStringArray;
|
|
|
54
54
|
exports.ok = ok;
|
|
55
55
|
exports.err = err;
|
|
56
56
|
exports.isExplicitCrossProjectSearch = isExplicitCrossProjectSearch;
|
|
57
|
+
exports.resolveMcpProject = resolveMcpProject;
|
|
58
|
+
exports.containMcpTarget = containMcpTarget;
|
|
59
|
+
exports.mcpRootPrefix = mcpRootPrefix;
|
|
60
|
+
exports.resolveMcpSourceFile = resolveMcpSourceFile;
|
|
57
61
|
exports.searchResultPath = searchResultPath;
|
|
58
62
|
exports.searchResultStartLine = searchResultStartLine;
|
|
59
63
|
exports.searchResultEndLine = searchResultEndLine;
|
|
@@ -71,6 +75,7 @@ const config_1 = require("../config");
|
|
|
71
75
|
const surprising_connections_1 = require("../lib/analysis/surprising-connections");
|
|
72
76
|
const languages_1 = require("../lib/core/languages");
|
|
73
77
|
const graph_builder_1 = require("../lib/graph/graph-builder");
|
|
78
|
+
const embedding_status_1 = require("../lib/index/embedding-status");
|
|
74
79
|
const index_config_1 = require("../lib/index/index-config");
|
|
75
80
|
const syncer_1 = require("../lib/index/syncer");
|
|
76
81
|
const agent_search_formatter_1 = require("../lib/output/agent-search-formatter");
|
|
@@ -81,12 +86,15 @@ const skeletonizer_1 = require("../lib/skeleton/skeletonizer");
|
|
|
81
86
|
const symbol_extractor_1 = require("../lib/skeleton/symbol-extractor");
|
|
82
87
|
const meta_cache_1 = require("../lib/store/meta-cache");
|
|
83
88
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
89
|
+
const cross_project_1 = require("../lib/utils/cross-project");
|
|
84
90
|
const file_utils_1 = require("../lib/utils/file-utils");
|
|
85
91
|
const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
86
92
|
const format_helpers_1 = require("../lib/utils/format-helpers");
|
|
87
93
|
const import_extractor_1 = require("../lib/utils/import-extractor");
|
|
94
|
+
const path_containment_1 = require("../lib/utils/path-containment");
|
|
88
95
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
89
96
|
const project_root_1 = require("../lib/utils/project-root");
|
|
97
|
+
const scope_filter_1 = require("../lib/utils/scope-filter");
|
|
90
98
|
const watcher_launcher_1 = require("../lib/utils/watcher-launcher");
|
|
91
99
|
const watcher_store_1 = require("../lib/utils/watcher-store");
|
|
92
100
|
const audit_1 = require("./audit");
|
|
@@ -120,6 +128,44 @@ function isExplicitCrossProjectSearch(args, isSearchAll = false) {
|
|
|
120
128
|
(typeof args.exclude_projects === "string" &&
|
|
121
129
|
args.exclude_projects.trim() !== ""));
|
|
122
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
|
+
}
|
|
123
169
|
function chunkAbsPath(chunk) {
|
|
124
170
|
const metadata = chunk.metadata;
|
|
125
171
|
return String(chunk.path || (metadata === null || metadata === void 0 ? void 0 : metadata.path) || "");
|
|
@@ -315,12 +361,16 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
315
361
|
});
|
|
316
362
|
}
|
|
317
363
|
// --- Background watcher ---
|
|
318
|
-
function ensureWatcher() {
|
|
364
|
+
function ensureWatcher(root) {
|
|
319
365
|
return __awaiter(this, void 0, void 0, function* () {
|
|
366
|
+
var _a;
|
|
320
367
|
try {
|
|
321
|
-
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);
|
|
322
372
|
if (result.ok && !result.reused) {
|
|
323
|
-
console.log(`[MCP] Started background watcher for ${
|
|
373
|
+
console.log(`[MCP] Started background watcher for ${watchRoot} (PID: ${result.pid})`);
|
|
324
374
|
}
|
|
325
375
|
}
|
|
326
376
|
catch (err) {
|
|
@@ -335,29 +385,32 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
335
385
|
// exact registry match; otherwise walk the registry for an ancestor that
|
|
336
386
|
// covers the cwd, and scope to it instead of silently widening to the whole
|
|
337
387
|
// index. `root` is the registered project root to scope to.
|
|
338
|
-
function resolveRegisteredProject() {
|
|
339
|
-
const
|
|
340
|
-
if (
|
|
341
|
-
return { proj:
|
|
342
|
-
const parent = (0, project_registry_1.getParentProject)(projectRoot);
|
|
343
|
-
if (parent)
|
|
344
|
-
return { proj: parent, root: parent.root };
|
|
388
|
+
function resolveRegisteredProject(requested) {
|
|
389
|
+
const project = resolveMcpProject(requested, projectRoot);
|
|
390
|
+
if (project)
|
|
391
|
+
return { proj: project, root: project.root };
|
|
345
392
|
return { proj: undefined, root: projectRoot };
|
|
346
393
|
}
|
|
347
394
|
// --- Tool handlers ---
|
|
348
395
|
function handleSemanticSearch(args_1) {
|
|
349
396
|
return __awaiter(this, arguments, void 0, function* (args, isSearchAll = false) {
|
|
397
|
+
var _a, _b;
|
|
350
398
|
const query = String(args.query || "");
|
|
351
399
|
if (!query)
|
|
352
400
|
return err("Missing required parameter: query");
|
|
353
401
|
const searchAll = isExplicitCrossProjectSearch(args, isSearchAll);
|
|
354
402
|
const limit = Math.min(Math.max(Number(args.limit) || 3, 1), 50);
|
|
355
|
-
ensureWatcher();
|
|
356
403
|
// Project resolution. The server is pinned to whatever cwd it launched in
|
|
357
404
|
// (resolved once at startup). Resolve that cwd to its registered project —
|
|
358
405
|
// exact match, or a registered ancestor when the cwd is a subdirectory of
|
|
359
406
|
// an umbrella project. Searches scope to `resolvedRoot`.
|
|
360
|
-
const { proj, root: resolvedRoot } = resolveRegisteredProject();
|
|
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
|
+
}
|
|
361
414
|
// Cross-project search (the whole index) is opt-in: it happens only when
|
|
362
415
|
// the caller passes scope:"all" or projects:"…". Otherwise we require a
|
|
363
416
|
// resolved project and error loudly when there isn't one — never silently
|
|
@@ -378,34 +431,25 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
378
431
|
if (proj.status === "pending" || proj.chunkCount === 0) {
|
|
379
432
|
return err("Project not indexed yet. Run `gmax add` to index it first.");
|
|
380
433
|
}
|
|
434
|
+
void ensureWatcher(resolvedRoot);
|
|
381
435
|
}
|
|
382
436
|
try {
|
|
383
|
-
const searcher = getSearcher();
|
|
384
437
|
// Determine path prefix and display root for relative paths
|
|
385
438
|
let pathPrefix;
|
|
386
|
-
|
|
439
|
+
const displayRoot = resolvedRoot;
|
|
440
|
+
let projectScope;
|
|
387
441
|
if (!searchAll) {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
: path.
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
displayRoot = searchRoot;
|
|
395
|
-
pathPrefix = searchRoot.endsWith("/") ? searchRoot : `${searchRoot}/`;
|
|
396
|
-
if (typeof args.path === "string") {
|
|
397
|
-
pathPrefix = path.join(searchRoot, args.path);
|
|
398
|
-
if (!pathPrefix.endsWith("/"))
|
|
399
|
-
pathPrefix += "/";
|
|
400
|
-
}
|
|
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;
|
|
401
448
|
}
|
|
402
449
|
const filters = {};
|
|
403
450
|
if (typeof args.file === "string" && args.file) {
|
|
404
451
|
filters.file = args.file;
|
|
405
452
|
}
|
|
406
|
-
if (typeof args.exclude === "string" && args.exclude) {
|
|
407
|
-
filters.exclude = args.exclude;
|
|
408
|
-
}
|
|
409
453
|
if (typeof args.language === "string" && args.language) {
|
|
410
454
|
filters.language = args.language;
|
|
411
455
|
}
|
|
@@ -413,29 +457,32 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
413
457
|
filters.role = args.role;
|
|
414
458
|
}
|
|
415
459
|
if (searchAll) {
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
.
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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.");
|
|
425
470
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
if (roots.length > 0) {
|
|
435
|
-
filters.exclude_project_roots = roots.join(",");
|
|
436
|
-
}
|
|
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;
|
|
437
479
|
}
|
|
438
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();
|
|
439
486
|
// Aider-style seeding: the agent passes its open files / discussed
|
|
440
487
|
// symbols; the searcher biases candidate generation toward them.
|
|
441
488
|
const parseSeedList = (v) => {
|
|
@@ -454,6 +501,14 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
454
501
|
? { files: seedFiles, symbols: seedSymbols }
|
|
455
502
|
: undefined;
|
|
456
503
|
const result = yield searcher.search(query, limit, { rerank: process.env.GMAX_RERANK === "1", seeds }, Object.keys(filters).length > 0 ? filters : undefined, pathPrefix);
|
|
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
|
+
});
|
|
457
512
|
// Prepend any searcher warnings to whatever body we return.
|
|
458
513
|
const prefixNotes = (body) => {
|
|
459
514
|
var _a;
|
|
@@ -474,8 +529,11 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
474
529
|
var _a;
|
|
475
530
|
if (!includeImports || !absPath)
|
|
476
531
|
return "";
|
|
532
|
+
const sourcePath = resolveMcpSourceFile(allowedRoots, absPath);
|
|
533
|
+
if (!sourcePath)
|
|
534
|
+
return "";
|
|
477
535
|
if (!importCache.has(absPath)) {
|
|
478
|
-
importCache.set(absPath, (0, import_extractor_1.extractImports)(
|
|
536
|
+
importCache.set(absPath, (0, import_extractor_1.extractImports)(sourcePath));
|
|
479
537
|
}
|
|
480
538
|
return (_a = importCache.get(absPath)) !== null && _a !== void 0 ? _a : "";
|
|
481
539
|
};
|
|
@@ -519,7 +577,10 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
519
577
|
if (contextN > 0 && absPath) {
|
|
520
578
|
// Read surrounding context from file
|
|
521
579
|
try {
|
|
522
|
-
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");
|
|
523
584
|
const fileLines = fileContent.split("\n");
|
|
524
585
|
const ctxStart = Math.max(0, startLine - contextN);
|
|
525
586
|
const ctxEnd = Math.min(fileLines.length, endLine + 1 + contextN);
|
|
@@ -584,7 +645,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
584
645
|
const regex = new RegExp(namePattern, "i");
|
|
585
646
|
results = results.filter((r) => r.symbols.some((s) => regex.test(s)));
|
|
586
647
|
}
|
|
587
|
-
catch (
|
|
648
|
+
catch (_c) {
|
|
588
649
|
// Invalid regex — skip filter
|
|
589
650
|
}
|
|
590
651
|
}
|
|
@@ -593,19 +654,19 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
593
654
|
if (mode === "symbol" && !searchAll) {
|
|
594
655
|
try {
|
|
595
656
|
const db = getVectorDb();
|
|
596
|
-
const builder = new graph_builder_1.GraphBuilder(db);
|
|
657
|
+
const builder = new graph_builder_1.GraphBuilder(db, resolvedRoot);
|
|
597
658
|
const graph = yield builder.buildGraph(query);
|
|
598
659
|
if (graph.center) {
|
|
599
660
|
const traceLines = ["", "--- Call graph ---"];
|
|
600
|
-
const centerRel = graph.center.file.startsWith(
|
|
601
|
-
? graph.center.file.slice(
|
|
661
|
+
const centerRel = graph.center.file.startsWith(resolvedRoot)
|
|
662
|
+
? graph.center.file.slice(resolvedRoot.length + 1)
|
|
602
663
|
: graph.center.file;
|
|
603
664
|
traceLines.push(`${graph.center.symbol} [${graph.center.role}] ${centerRel}:${graph.center.line + 1}`);
|
|
604
665
|
if (graph.callers.length > 0) {
|
|
605
666
|
traceLines.push("Callers:");
|
|
606
667
|
for (const caller of graph.callers) {
|
|
607
|
-
const rel = caller.file.startsWith(
|
|
608
|
-
? caller.file.slice(
|
|
668
|
+
const rel = caller.file.startsWith(resolvedRoot)
|
|
669
|
+
? caller.file.slice(resolvedRoot.length + 1)
|
|
609
670
|
: caller.file;
|
|
610
671
|
traceLines.push(` <- ${caller.symbol} ${rel}:${caller.line + 1}`);
|
|
611
672
|
}
|
|
@@ -614,8 +675,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
614
675
|
traceLines.push("Calls:");
|
|
615
676
|
for (const callee of graph.callees.slice(0, 15)) {
|
|
616
677
|
if (callee.file) {
|
|
617
|
-
const rel = callee.file.startsWith(
|
|
618
|
-
? callee.file.slice(
|
|
678
|
+
const rel = callee.file.startsWith(resolvedRoot)
|
|
679
|
+
? callee.file.slice(resolvedRoot.length + 1)
|
|
619
680
|
: callee.file;
|
|
620
681
|
traceLines.push(` -> ${callee.symbol} ${rel}:${callee.line + 1}`);
|
|
621
682
|
}
|
|
@@ -627,7 +688,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
627
688
|
output += `\n${traceLines.join("\n")}`;
|
|
628
689
|
}
|
|
629
690
|
}
|
|
630
|
-
catch (
|
|
691
|
+
catch (_d) {
|
|
631
692
|
// Trace failed — return search results without trace
|
|
632
693
|
}
|
|
633
694
|
}
|
|
@@ -641,10 +702,14 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
641
702
|
}
|
|
642
703
|
function handleCodeSkeleton(args) {
|
|
643
704
|
return __awaiter(this, void 0, void 0, function* () {
|
|
644
|
-
ensureWatcher();
|
|
645
705
|
const target = String(args.target || "");
|
|
646
706
|
if (!target)
|
|
647
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);
|
|
648
713
|
const fileLimit = Math.min(Math.max(Number(args.limit) || 10, 1), 20);
|
|
649
714
|
// Determine targets: comma-separated, directory, or single file
|
|
650
715
|
let targets;
|
|
@@ -655,12 +720,14 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
655
720
|
.filter(Boolean);
|
|
656
721
|
}
|
|
657
722
|
else {
|
|
658
|
-
const absPath =
|
|
723
|
+
const absPath = (0, path_containment_1.resolveContainedPath)(root, target, {
|
|
724
|
+
verifyExistingTarget: true,
|
|
725
|
+
});
|
|
659
726
|
if (fs.existsSync(absPath) && fs.statSync(absPath).isDirectory()) {
|
|
660
727
|
const entries = fs.readdirSync(absPath, { withFileTypes: true });
|
|
661
728
|
targets = entries
|
|
662
729
|
.filter((e) => e.isFile() && (0, file_utils_1.isIndexableFile)(path.join(absPath, e.name)))
|
|
663
|
-
.map((e) => path.relative(
|
|
730
|
+
.map((e) => path.relative(root, path.join(absPath, e.name)))
|
|
664
731
|
.slice(0, fileLimit);
|
|
665
732
|
if (targets.length === 0) {
|
|
666
733
|
return err(`No indexable files found in ${target}`);
|
|
@@ -676,8 +743,11 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
676
743
|
const jsonFiles = [];
|
|
677
744
|
const skel = yield getSkeletonizer();
|
|
678
745
|
for (const t of targets) {
|
|
679
|
-
|
|
680
|
-
|
|
746
|
+
let absPath;
|
|
747
|
+
try {
|
|
748
|
+
absPath = (0, path_containment_1.resolveContainedFile)(root, t);
|
|
749
|
+
}
|
|
750
|
+
catch (_a) {
|
|
681
751
|
if (fmt !== "json")
|
|
682
752
|
parts.push(`// ${t} — file not found`);
|
|
683
753
|
continue;
|
|
@@ -687,7 +757,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
687
757
|
try {
|
|
688
758
|
sourceContent = fs.readFileSync(absPath, "utf-8");
|
|
689
759
|
}
|
|
690
|
-
catch (
|
|
760
|
+
catch (_b) { }
|
|
691
761
|
let skeleton = "";
|
|
692
762
|
let language = "";
|
|
693
763
|
let tokenEstimate = 0;
|
|
@@ -700,7 +770,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
700
770
|
tokenEstimate = Math.ceil(cached.length / 4);
|
|
701
771
|
}
|
|
702
772
|
}
|
|
703
|
-
catch (
|
|
773
|
+
catch (_c) { }
|
|
704
774
|
// Generate live if no cache
|
|
705
775
|
if (!skeleton) {
|
|
706
776
|
try {
|
|
@@ -753,17 +823,17 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
753
823
|
}
|
|
754
824
|
function handleTraceCalls(args) {
|
|
755
825
|
return __awaiter(this, void 0, void 0, function* () {
|
|
756
|
-
ensureWatcher();
|
|
757
826
|
const symbol = String(args.symbol || "");
|
|
758
827
|
if (!symbol)
|
|
759
828
|
return err("Missing required parameter: symbol");
|
|
760
|
-
const { proj } = resolveRegisteredProject();
|
|
829
|
+
const { proj, root } = resolveRegisteredProject();
|
|
761
830
|
if (!proj) {
|
|
762
831
|
return err("Project not added to gmax yet. Run `gmax add` to index it first.");
|
|
763
832
|
}
|
|
833
|
+
void ensureWatcher(root);
|
|
764
834
|
try {
|
|
765
835
|
const db = getVectorDb();
|
|
766
|
-
const builder = new graph_builder_1.GraphBuilder(db);
|
|
836
|
+
const builder = new graph_builder_1.GraphBuilder(db, root);
|
|
767
837
|
const depth = Math.min(Math.max(Number(args.depth) || 1, 1), 3);
|
|
768
838
|
const graph = yield builder.buildGraphMultiHop(symbol, depth);
|
|
769
839
|
if (!graph.center) {
|
|
@@ -780,8 +850,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
780
850
|
if (filteredImporters.length > 0) {
|
|
781
851
|
lines.push("Imported by:");
|
|
782
852
|
for (const imp of filteredImporters.slice(0, 10)) {
|
|
783
|
-
const rel = imp.startsWith(
|
|
784
|
-
? imp.slice(
|
|
853
|
+
const rel = imp.startsWith(root)
|
|
854
|
+
? imp.slice(root.length + 1)
|
|
785
855
|
: imp;
|
|
786
856
|
lines.push(` ${rel}`);
|
|
787
857
|
}
|
|
@@ -790,8 +860,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
790
860
|
// Callers (recursive tree)
|
|
791
861
|
function formatCallerTree(trees, indent) {
|
|
792
862
|
for (const t of trees) {
|
|
793
|
-
const rel = t.node.file.startsWith(
|
|
794
|
-
? t.node.file.slice(
|
|
863
|
+
const rel = t.node.file.startsWith(root)
|
|
864
|
+
? t.node.file.slice(root.length + 1)
|
|
795
865
|
: t.node.file;
|
|
796
866
|
const pad = " ".repeat(indent);
|
|
797
867
|
lines.push(`${pad}<- ${t.node.symbol} ${rel}:${t.node.line + 1}`);
|
|
@@ -810,8 +880,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
810
880
|
lines.push("Calls:");
|
|
811
881
|
for (const callee of graph.callees.slice(0, 15)) {
|
|
812
882
|
if (callee.file) {
|
|
813
|
-
const rel = callee.file.startsWith(
|
|
814
|
-
? callee.file.slice(
|
|
883
|
+
const rel = callee.file.startsWith(root)
|
|
884
|
+
? callee.file.slice(root.length + 1)
|
|
815
885
|
: callee.file;
|
|
816
886
|
lines.push(` -> ${callee.symbol} ${rel}:${callee.line + 1}`);
|
|
817
887
|
}
|
|
@@ -841,7 +911,9 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
841
911
|
if (!symbol)
|
|
842
912
|
return err("Missing required parameter: symbol");
|
|
843
913
|
try {
|
|
844
|
-
const
|
|
914
|
+
const { proj, root } = resolveRegisteredProject(args.root);
|
|
915
|
+
if (!proj)
|
|
916
|
+
return err("Project not added to gmax yet.");
|
|
845
917
|
const db = getVectorDb();
|
|
846
918
|
const table = yield db.ensureTable();
|
|
847
919
|
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
@@ -881,7 +953,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
881
953
|
return ((ROLE_PRI[String(b.role)] || 0) - (ROLE_PRI[String(a.role)] || 0));
|
|
882
954
|
});
|
|
883
955
|
const best = sorted[0];
|
|
884
|
-
const filePath = String(best.path);
|
|
956
|
+
const filePath = (0, path_containment_1.resolveContainedFile)(root, String(best.path));
|
|
885
957
|
const startLine = Number(best.start_line || 0);
|
|
886
958
|
const endLine = Number(best.end_line || 0);
|
|
887
959
|
const role = String(best.role || "IMPLEMENTATION");
|
|
@@ -932,7 +1004,9 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
932
1004
|
if (!symbol)
|
|
933
1005
|
return err("Missing required parameter: symbol");
|
|
934
1006
|
try {
|
|
935
|
-
const
|
|
1007
|
+
const { proj, root } = resolveRegisteredProject(args.root);
|
|
1008
|
+
if (!proj)
|
|
1009
|
+
return err("Project not added to gmax yet.");
|
|
936
1010
|
const depth = Math.min(Math.max(Number(args.depth || 1), 1), 3);
|
|
937
1011
|
const db = getVectorDb();
|
|
938
1012
|
const { GraphBuilder } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/graph-builder")));
|
|
@@ -964,7 +1038,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
964
1038
|
let sigText = "(source not available)";
|
|
965
1039
|
let bodyLines = 0;
|
|
966
1040
|
try {
|
|
967
|
-
const
|
|
1041
|
+
const centerPath = (0, path_containment_1.resolveContainedFile)(root, center.file);
|
|
1042
|
+
const content = fs.readFileSync(centerPath, "utf-8");
|
|
968
1043
|
const lines = content.split("\n");
|
|
969
1044
|
const chunk = lines.slice(startLine, endLine + 1);
|
|
970
1045
|
bodyLines = chunk.length;
|
|
@@ -1063,7 +1138,9 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1063
1138
|
if (!symbol)
|
|
1064
1139
|
return err("Missing required parameter: symbol");
|
|
1065
1140
|
try {
|
|
1066
|
-
const
|
|
1141
|
+
const { proj, root } = resolveRegisteredProject(args.root);
|
|
1142
|
+
if (!proj)
|
|
1143
|
+
return err("Project not added to gmax yet.");
|
|
1067
1144
|
const db = getVectorDb();
|
|
1068
1145
|
const table = yield db.ensureTable();
|
|
1069
1146
|
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
@@ -1108,9 +1185,9 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1108
1185
|
function handleAudit(args) {
|
|
1109
1186
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1110
1187
|
ensureWatcher();
|
|
1111
|
-
const
|
|
1112
|
-
|
|
1113
|
-
|
|
1188
|
+
const { proj, root } = resolveRegisteredProject(args.root);
|
|
1189
|
+
if (!proj)
|
|
1190
|
+
return err("Project not added to gmax yet.");
|
|
1114
1191
|
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
1115
1192
|
const top = Math.min(Math.max(Number(args.top) || 10, 1), 50);
|
|
1116
1193
|
try {
|
|
@@ -1193,9 +1270,9 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1193
1270
|
if (args.experimental !== true) {
|
|
1194
1271
|
return err("surprising_connections is experimental; pass experimental:true.");
|
|
1195
1272
|
}
|
|
1196
|
-
const
|
|
1197
|
-
|
|
1198
|
-
|
|
1273
|
+
const { proj, root } = resolveRegisteredProject(args.root);
|
|
1274
|
+
if (!proj)
|
|
1275
|
+
return err("Project not added to gmax yet.");
|
|
1199
1276
|
const top = Math.min(Math.max(Number(args.top) || 10, 1), 100);
|
|
1200
1277
|
const sample = Math.min(Math.max(Number(args.sample) || surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.sample, 1), 10000);
|
|
1201
1278
|
const neighbors = Math.min(Math.max(Number(args.neighbors) || surprising_connections_1.DEFAULT_SURPRISE_OPTIONS.neighbors, 1), 200);
|
|
@@ -1233,7 +1310,9 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1233
1310
|
const direction = args.direction === "callers" ? "callers" : "callees";
|
|
1234
1311
|
const maxHops = Math.min(Math.max(Number(args.max_hops) || 2, 1), 5);
|
|
1235
1312
|
try {
|
|
1236
|
-
const
|
|
1313
|
+
const { proj, root } = resolveRegisteredProject(args.root);
|
|
1314
|
+
if (!proj)
|
|
1315
|
+
return err("Project not added to gmax yet.");
|
|
1237
1316
|
const builder = new graph_builder_1.GraphBuilder(getVectorDb(), root);
|
|
1238
1317
|
const hits = yield builder.getNeighbors(symbol, direction, maxHops);
|
|
1239
1318
|
if (hits.length === 0) {
|
|
@@ -1268,7 +1347,9 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1268
1347
|
const direction = args.direction === "callers" ? "callers" : "callees";
|
|
1269
1348
|
const maxHops = Math.min(Math.max(Number(args.max_hops) || 6, 1), 10);
|
|
1270
1349
|
try {
|
|
1271
|
-
const
|
|
1350
|
+
const { proj, root } = resolveRegisteredProject(args.root);
|
|
1351
|
+
if (!proj)
|
|
1352
|
+
return err("Project not added to gmax yet.");
|
|
1272
1353
|
const builder = new graph_builder_1.GraphBuilder(getVectorDb(), root);
|
|
1273
1354
|
const pathSyms = yield builder.findPaths(from, to, direction, maxHops);
|
|
1274
1355
|
if (!pathSyms) {
|
|
@@ -1294,8 +1375,10 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1294
1375
|
return err("Missing required parameter: files (non-empty array)");
|
|
1295
1376
|
}
|
|
1296
1377
|
try {
|
|
1297
|
-
const
|
|
1298
|
-
|
|
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 }));
|
|
1299
1382
|
const builder = new graph_builder_1.GraphBuilder(getVectorDb(), root);
|
|
1300
1383
|
const sg = yield builder.subgraphForFiles(abs);
|
|
1301
1384
|
if (sg.symbols.length === 0) {
|
|
@@ -1336,14 +1419,20 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1336
1419
|
const pattern = typeof args.pattern === "string" ? args.pattern : undefined;
|
|
1337
1420
|
const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 100);
|
|
1338
1421
|
const pathPrefix = typeof args.path === "string" ? args.path : undefined;
|
|
1339
|
-
const { proj } = resolveRegisteredProject();
|
|
1422
|
+
const { proj, root } = resolveRegisteredProject();
|
|
1340
1423
|
if (!proj) {
|
|
1341
1424
|
return err("Project not added to gmax yet. Run `gmax add` to index it first.");
|
|
1342
1425
|
}
|
|
1343
1426
|
try {
|
|
1344
1427
|
const db = getVectorDb();
|
|
1345
1428
|
const table = yield db.ensureTable();
|
|
1346
|
-
|
|
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
|
|
1347
1436
|
.query()
|
|
1348
1437
|
.select([
|
|
1349
1438
|
"defined_symbols",
|
|
@@ -1352,15 +1441,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1352
1441
|
"role",
|
|
1353
1442
|
"is_exported",
|
|
1354
1443
|
])
|
|
1355
|
-
.where(
|
|
1444
|
+
.where(where)
|
|
1356
1445
|
.limit(pattern ? 10000 : Math.max(limit * 50, 2000));
|
|
1357
|
-
if (pathPrefix) {
|
|
1358
|
-
// Support both absolute and relative path prefixes
|
|
1359
|
-
const absPrefix = path.isAbsolute(pathPrefix)
|
|
1360
|
-
? pathPrefix
|
|
1361
|
-
: path.resolve(projectRoot, pathPrefix);
|
|
1362
|
-
query = query.where((0, filter_builder_1.pathStartsWith)((0, filter_builder_1.normalizePath)(absPrefix)));
|
|
1363
|
-
}
|
|
1364
1446
|
const rows = yield query.toArray();
|
|
1365
1447
|
const map = new Map();
|
|
1366
1448
|
for (const row of rows) {
|
|
@@ -1400,8 +1482,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1400
1482
|
return ok("No symbols found. Run `gmax status` to verify the project is indexed, or `gmax index` to rebuild.");
|
|
1401
1483
|
}
|
|
1402
1484
|
const lines = entries.map((e) => {
|
|
1403
|
-
const rel = e.path.startsWith(
|
|
1404
|
-
? e.path.slice(
|
|
1485
|
+
const rel = e.path.startsWith(root)
|
|
1486
|
+
? e.path.slice(root.length + 1)
|
|
1405
1487
|
: e.path;
|
|
1406
1488
|
const roleTag = e.role ? ` [${e.role.slice(0, 4)}]` : "";
|
|
1407
1489
|
const expTag = e.exported ? " exported" : "";
|
|
@@ -1423,13 +1505,15 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1423
1505
|
if (projects.length === 0) {
|
|
1424
1506
|
return ok("No projects indexed yet. cd into a repo and run `gmax add` to index it.");
|
|
1425
1507
|
}
|
|
1426
|
-
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)();
|
|
1427
1510
|
const lines = projects.map((p) => {
|
|
1428
1511
|
const here = p.name === currentName ? " (current)" : "";
|
|
1429
1512
|
const chunks = typeof p.chunkCount === "number"
|
|
1430
1513
|
? `\t(${p.chunkCount} chunks)`
|
|
1431
1514
|
: "";
|
|
1432
|
-
|
|
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}`;
|
|
1433
1517
|
});
|
|
1434
1518
|
return ok(`${projects.length} indexed project(s). ` +
|
|
1435
1519
|
`Pass a name to semantic_search via projects:"name", or use scope:"all".\n\n` +
|
|
@@ -1443,11 +1527,12 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1443
1527
|
}
|
|
1444
1528
|
function handleIndexStatus() {
|
|
1445
1529
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1446
|
-
var _a
|
|
1530
|
+
var _a;
|
|
1447
1531
|
try {
|
|
1448
|
-
const config = (0, index_config_1.readIndexConfig)(config_1.PATHS.configPath);
|
|
1449
1532
|
const globalConfig = (0, index_config_1.readGlobalConfig)();
|
|
1450
1533
|
const projects = (0, project_registry_1.listProjects)();
|
|
1534
|
+
const currentProject = resolveMcpProject(undefined, projectRoot);
|
|
1535
|
+
const identity = (0, embedding_status_1.projectEmbeddingStatus)(currentProject, globalConfig);
|
|
1451
1536
|
const db = getVectorDb();
|
|
1452
1537
|
const stats = yield db.getStats();
|
|
1453
1538
|
const fileCount = yield db.getDistinctFileCount();
|
|
@@ -1470,8 +1555,13 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1470
1555
|
// focused on index health and avoid the N-project LanceDB scans.
|
|
1471
1556
|
const lines = [
|
|
1472
1557
|
`Index: ~/.gmax/lancedb (${stats.chunks} chunks, ${fileCount} files)`,
|
|
1473
|
-
`
|
|
1474
|
-
|
|
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
|
+
: "",
|
|
1475
1565
|
watcherLine,
|
|
1476
1566
|
`Projects: ${projects.length} indexed (call list_projects for names + per-project chunk counts)`,
|
|
1477
1567
|
].filter(Boolean);
|
|
@@ -1485,7 +1575,12 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1485
1575
|
}
|
|
1486
1576
|
function handleSummarizeDirectory(args) {
|
|
1487
1577
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1488
|
-
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;
|
|
1489
1584
|
const prefix = dir.endsWith("/") ? dir : `${dir}/`;
|
|
1490
1585
|
const limit = Math.min(Math.max(Number(args.limit) || 200, 1), 5000);
|
|
1491
1586
|
try {
|
|
@@ -1510,7 +1605,9 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1510
1605
|
function handleSummarizeProject(args) {
|
|
1511
1606
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1512
1607
|
var _a;
|
|
1513
|
-
const
|
|
1608
|
+
const { proj, root } = resolveRegisteredProject(args.root);
|
|
1609
|
+
if (!proj)
|
|
1610
|
+
return err("Project not added to gmax yet.");
|
|
1514
1611
|
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
1515
1612
|
const projectName = path.basename(root);
|
|
1516
1613
|
try {
|
|
@@ -1632,7 +1729,13 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1632
1729
|
if (!file)
|
|
1633
1730
|
return err("Missing required parameter: file");
|
|
1634
1731
|
const limit = Math.min(Math.max(Number(args.limit) || 10, 1), 25);
|
|
1635
|
-
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}/`;
|
|
1636
1739
|
try {
|
|
1637
1740
|
const db = getVectorDb();
|
|
1638
1741
|
const table = yield db.ensureTable();
|
|
@@ -1660,7 +1763,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1660
1763
|
const rows = yield table
|
|
1661
1764
|
.query()
|
|
1662
1765
|
.select(["path"])
|
|
1663
|
-
.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)}`)
|
|
1664
1767
|
.limit(3)
|
|
1665
1768
|
.toArray();
|
|
1666
1769
|
for (const row of rows) {
|
|
@@ -1676,7 +1779,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1676
1779
|
const rows = yield table
|
|
1677
1780
|
.query()
|
|
1678
1781
|
.select(["path"])
|
|
1679
|
-
.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)}`)
|
|
1680
1783
|
.limit(20)
|
|
1681
1784
|
.toArray();
|
|
1682
1785
|
for (const row of rows) {
|
|
@@ -1694,8 +1797,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1694
1797
|
if (topDeps.length > 0) {
|
|
1695
1798
|
lines.push("Dependencies (files this imports/calls):");
|
|
1696
1799
|
for (const [p, count] of topDeps) {
|
|
1697
|
-
const rel = p.startsWith(
|
|
1698
|
-
? p.slice(
|
|
1800
|
+
const rel = p.startsWith(rootPrefix)
|
|
1801
|
+
? p.slice(rootPrefix.length)
|
|
1699
1802
|
: p;
|
|
1700
1803
|
lines.push(` ${rel.padEnd(40)} (${count} shared symbol${count > 1 ? "s" : ""})`);
|
|
1701
1804
|
}
|
|
@@ -1710,8 +1813,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1710
1813
|
if (topRevs.length > 0) {
|
|
1711
1814
|
lines.push("Dependents (files that call this):");
|
|
1712
1815
|
for (const [p, count] of topRevs) {
|
|
1713
|
-
const rel = p.startsWith(
|
|
1714
|
-
? p.slice(
|
|
1816
|
+
const rel = p.startsWith(rootPrefix)
|
|
1817
|
+
? p.slice(rootPrefix.length)
|
|
1715
1818
|
: p;
|
|
1716
1819
|
lines.push(` ${rel.padEnd(40)} (${count} shared symbol${count > 1 ? "s" : ""})`);
|
|
1717
1820
|
}
|
|
@@ -1732,7 +1835,9 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1732
1835
|
var _a, e_1, _b, _c;
|
|
1733
1836
|
ensureWatcher();
|
|
1734
1837
|
const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 50);
|
|
1735
|
-
const
|
|
1838
|
+
const { proj, root } = resolveRegisteredProject(args.root);
|
|
1839
|
+
if (!proj)
|
|
1840
|
+
return err("Project not added to gmax yet.");
|
|
1736
1841
|
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
1737
1842
|
try {
|
|
1738
1843
|
const metaCache = new meta_cache_1.MetaCache(config_1.PATHS.lmdbPath);
|
|
@@ -1793,16 +1898,20 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1793
1898
|
const limit = Math.min(Math.max(Number(args.limit) || 10, 1), 50);
|
|
1794
1899
|
const role = typeof args.role === "string" ? args.role.toUpperCase() : undefined;
|
|
1795
1900
|
try {
|
|
1796
|
-
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);
|
|
1797
1906
|
if (changedFiles.length === 0) {
|
|
1798
1907
|
return ok(ref
|
|
1799
1908
|
? `No changes found relative to ${ref}.`
|
|
1800
1909
|
: "No uncommitted changes found.");
|
|
1801
1910
|
}
|
|
1802
|
-
const rel = (p) => p.startsWith(`${
|
|
1911
|
+
const rel = (p) => p.startsWith(`${root}/`) ? p.slice(root.length + 1) : p;
|
|
1803
1912
|
if (query) {
|
|
1804
1913
|
const searcher = getSearcher();
|
|
1805
|
-
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);
|
|
1806
1915
|
const changedSet = new Set(changedFiles);
|
|
1807
1916
|
// searcher.search() returns mapped chunks (path under metadata.path);
|
|
1808
1917
|
// changedFiles are absolute, so match on the resolved absolute path.
|
|
@@ -1850,15 +1959,19 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1850
1959
|
return err("Missing required parameter: target");
|
|
1851
1960
|
const depth = Math.min(Math.max(Number(args.depth) || 1, 1), 3);
|
|
1852
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);
|
|
1853
1966
|
const { resolveTargetSymbols, findTests } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/impact")));
|
|
1854
1967
|
const db = getVectorDb();
|
|
1855
|
-
const { symbols, symbolFamilies } = yield resolveTargetSymbols(
|
|
1968
|
+
const { symbols, symbolFamilies } = yield resolveTargetSymbols(scopedTarget, db, mcpRootPrefix(root));
|
|
1856
1969
|
if (symbols.length === 0)
|
|
1857
1970
|
return ok(`No symbols found for: ${target}`);
|
|
1858
|
-
const tests = yield findTests(symbols, db,
|
|
1971
|
+
const tests = yield findTests(symbols, db, root, depth, undefined, symbolFamilies);
|
|
1859
1972
|
if (tests.length === 0)
|
|
1860
1973
|
return ok(`No tests found for ${target}.`);
|
|
1861
|
-
const rel = (p) => p.startsWith(`${
|
|
1974
|
+
const rel = (p) => p.startsWith(`${root}/`) ? p.slice(root.length + 1) : p;
|
|
1862
1975
|
const lines = tests.map((t) => {
|
|
1863
1976
|
const hop = t.hops === 0 ? "direct" : `${t.hops}-hop`;
|
|
1864
1977
|
return `${rel(t.file)}:${t.line + 1} ${t.symbol} (${hop})`;
|
|
@@ -1880,22 +1993,26 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1880
1993
|
const rollup = args.rollup === true;
|
|
1881
1994
|
const top = Math.min(Math.max(Number(args.top) || 10, 1), 100);
|
|
1882
1995
|
try {
|
|
1996
|
+
const { proj, root } = resolveRegisteredProject();
|
|
1997
|
+
if (!proj)
|
|
1998
|
+
return err("Project not added to gmax yet.");
|
|
1999
|
+
const scopedTarget = containMcpTarget(root, target);
|
|
1883
2000
|
const { resolveTargetSymbols, findTests, findDependents, findDependentsDetailed, isTestPath, } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/impact")));
|
|
1884
2001
|
const { buildImpactRollup, formatImpactRollupAgent } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/impact-rollup")));
|
|
1885
2002
|
const db = getVectorDb();
|
|
1886
|
-
const { symbols, resolvedAsFile, symbolFamilies } = yield resolveTargetSymbols(
|
|
2003
|
+
const { symbols, resolvedAsFile, symbolFamilies } = yield resolveTargetSymbols(scopedTarget, db, root);
|
|
1887
2004
|
if (symbols.length === 0)
|
|
1888
2005
|
return ok(`No symbols found for: ${target}`);
|
|
1889
2006
|
const targetPath = resolvedAsFile
|
|
1890
|
-
?
|
|
2007
|
+
? (0, path_containment_1.resolveContainedPath)(root, scopedTarget)
|
|
1891
2008
|
: undefined;
|
|
1892
2009
|
const excludePaths = targetPath ? new Set([targetPath]) : undefined;
|
|
1893
2010
|
const rollupLimit = Math.min(Math.max(top * 10, 100), 500);
|
|
1894
2011
|
const [dependents, tests] = yield Promise.all([
|
|
1895
2012
|
rollup
|
|
1896
|
-
? findDependentsDetailed(symbols, db,
|
|
1897
|
-
: findDependents(symbols, db,
|
|
1898
|
-
findTests(symbols, db,
|
|
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),
|
|
1899
2016
|
]);
|
|
1900
2017
|
if (rollup) {
|
|
1901
2018
|
const detailedDependents = dependents;
|
|
@@ -1903,17 +2020,17 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1903
2020
|
targetSymbols: symbols,
|
|
1904
2021
|
dependents: detailedDependents,
|
|
1905
2022
|
tests,
|
|
1906
|
-
projectRoot,
|
|
2023
|
+
projectRoot: root,
|
|
1907
2024
|
top,
|
|
1908
2025
|
});
|
|
1909
2026
|
return ok(formatImpactRollupAgent(impactRollup, {
|
|
1910
2027
|
target,
|
|
1911
|
-
projectRoot,
|
|
2028
|
+
projectRoot: root,
|
|
1912
2029
|
includeTests: true,
|
|
1913
2030
|
}));
|
|
1914
2031
|
}
|
|
1915
2032
|
const nonTestDeps = dependents.filter((d) => !isTestPath(d.file));
|
|
1916
|
-
const rel = (p) => p.startsWith(`${
|
|
2033
|
+
const rel = (p) => p.startsWith(`${root}/`) ? p.slice(root.length + 1) : p;
|
|
1917
2034
|
const sections = [`Impact analysis for ${target}:\n`];
|
|
1918
2035
|
if (nonTestDeps.length > 0) {
|
|
1919
2036
|
sections.push(`Dependents (${nonTestDeps.length}):`);
|
|
@@ -1950,13 +2067,17 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1950
2067
|
const limit = Math.min(Math.max(Number(args.limit) || 5, 1), 25);
|
|
1951
2068
|
const threshold = Number(args.threshold) || 0;
|
|
1952
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)());
|
|
1953
2074
|
const db = getVectorDb();
|
|
1954
2075
|
const table = yield db.ensureTable();
|
|
1955
2076
|
const isFile = target.includes("/") ||
|
|
1956
2077
|
(target.includes(".") && !target.includes(" "));
|
|
1957
2078
|
let sourceRows;
|
|
1958
2079
|
if (isFile) {
|
|
1959
|
-
const absPath =
|
|
2080
|
+
const absPath = (0, path_containment_1.resolveContainedPath)(root, target);
|
|
1960
2081
|
sourceRows = yield table
|
|
1961
2082
|
.query()
|
|
1962
2083
|
.select(["vector", "path", "start_line"])
|
|
@@ -1968,7 +2089,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1968
2089
|
sourceRows = yield table
|
|
1969
2090
|
.query()
|
|
1970
2091
|
.select(["vector", "path", "start_line"])
|
|
1971
|
-
.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}/`)}`)
|
|
1972
2093
|
.limit(1)
|
|
1973
2094
|
.toArray();
|
|
1974
2095
|
}
|
|
@@ -1988,7 +2109,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1988
2109
|
"role",
|
|
1989
2110
|
"_distance",
|
|
1990
2111
|
])
|
|
1991
|
-
.where((0, filter_builder_1.pathStartsWith)(`${
|
|
2112
|
+
.where((0, filter_builder_1.pathStartsWith)(`${root}/`))
|
|
1992
2113
|
.limit(limit + 5)
|
|
1993
2114
|
.toArray();
|
|
1994
2115
|
let filtered = results.filter((r) => !(r.path === source.path && r.start_line === source.start_line));
|
|
@@ -1996,7 +2117,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1996
2117
|
filtered = filtered.filter((r) => { var _a; return 1 / (1 + ((_a = r._distance) !== null && _a !== void 0 ? _a : 0)) >= threshold; });
|
|
1997
2118
|
if (filtered.length === 0)
|
|
1998
2119
|
return ok(`No similar code found for ${target}.`);
|
|
1999
|
-
const rel = (p) => p.startsWith(`${
|
|
2120
|
+
const rel = (p) => p.startsWith(`${root}/`) ? p.slice(root.length + 1) : p;
|
|
2000
2121
|
const lines = filtered.slice(0, limit).map((r) => {
|
|
2001
2122
|
var _a, _b, _c, _d;
|
|
2002
2123
|
const sym = (_b = (_a = toStringArray(r.defined_symbols)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : "";
|
|
@@ -2019,11 +2140,15 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2019
2140
|
const budget = Math.max(Number(args.budget) || 4000, 500);
|
|
2020
2141
|
const limit = Math.min(Math.max(Number(args.limit) || 10, 1), 25);
|
|
2021
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)());
|
|
2022
2147
|
const searcher = getSearcher();
|
|
2023
|
-
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);
|
|
2024
2149
|
if (response.data.length === 0)
|
|
2025
2150
|
return ok(`No results found for "${topic}".`);
|
|
2026
|
-
const rel = (p) => p.startsWith(`${
|
|
2151
|
+
const rel = (p) => p.startsWith(`${root}/`) ? p.slice(root.length + 1) : p;
|
|
2027
2152
|
const estTokens = (s) => Math.ceil(s.length / 4);
|
|
2028
2153
|
let tokensUsed = 0;
|
|
2029
2154
|
const sections = [];
|
|
@@ -2044,7 +2169,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2044
2169
|
const endLine = searchResultEndLine(r, startLine);
|
|
2045
2170
|
const sym = (_b = (_a = toStringArray(r.defined_symbols)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : "";
|
|
2046
2171
|
try {
|
|
2047
|
-
const
|
|
2172
|
+
const sourcePath = (0, path_containment_1.resolveContainedFile)(root, absP);
|
|
2173
|
+
const content = fs.readFileSync(sourcePath, "utf-8");
|
|
2048
2174
|
const body = content
|
|
2049
2175
|
.split("\n")
|
|
2050
2176
|
.slice(startLine, endLine + 1)
|
|
@@ -2216,8 +2342,17 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2216
2342
|
// the schema before the handler runs); handlers stay defensively coded.
|
|
2217
2343
|
function tool(name, config, handler) {
|
|
2218
2344
|
server.registerTool(name, config, (rawArgs) => __awaiter(this, void 0, void 0, function* () {
|
|
2219
|
-
|
|
2345
|
+
let args = (rawArgs !== null && rawArgs !== void 0 ? rawArgs : {});
|
|
2220
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
|
+
}
|
|
2221
2356
|
const result = yield handler(args);
|
|
2222
2357
|
yield logToolCall(name, args, startMs, result);
|
|
2223
2358
|
return result;
|
|
@@ -2235,7 +2370,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2235
2370
|
.optional(),
|
|
2236
2371
|
root: zod_1.z
|
|
2237
2372
|
.string()
|
|
2238
|
-
.describe("
|
|
2373
|
+
.describe("Registered project name or exact root")
|
|
2239
2374
|
.optional(),
|
|
2240
2375
|
path: zod_1.z
|
|
2241
2376
|
.string()
|