grepmax 0.9.4 → 0.10.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/dist/commands/config.js +26 -2
- package/dist/commands/extract.js +195 -0
- package/dist/commands/mcp.js +298 -15
- package/dist/commands/peek.js +254 -0
- package/dist/commands/project.js +40 -21
- package/dist/commands/recent.js +20 -7
- package/dist/commands/related.js +40 -21
- package/dist/commands/search.js +142 -22
- package/dist/commands/status.js +35 -6
- package/dist/commands/symbols.js +29 -2
- package/dist/commands/trace.js +33 -1
- package/dist/index.js +4 -0
- package/dist/lib/output/formatter.js +23 -3
- package/dist/lib/search/searcher.js +20 -10
- package/dist/lib/utils/query-log.js +65 -0
- package/package.json +1 -1
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/plugins/grepmax/hooks/start.js +1 -1
- package/plugins/grepmax/skills/grepmax/SKILL.md +55 -9
package/dist/commands/config.js
CHANGED
|
@@ -19,11 +19,13 @@ exports.config = new commander_1.Command("config")
|
|
|
19
19
|
.description("View or update gmax configuration")
|
|
20
20
|
.option("--embed-mode <mode>", "Set embedding mode: cpu or gpu")
|
|
21
21
|
.option("--model-tier <tier>", "Set model tier: small (384d) or standard (768d)")
|
|
22
|
+
.option("--query-log <on|off>", "Enable/disable query logging to ~/.gmax/logs/queries.jsonl")
|
|
22
23
|
.addHelpText("after", `
|
|
23
24
|
Examples:
|
|
24
25
|
gmax config Show current configuration
|
|
25
26
|
gmax config --embed-mode cpu Switch to CPU embeddings
|
|
26
27
|
gmax config --model-tier standard Switch to standard model (768d)
|
|
28
|
+
gmax config --query-log on Enable query logging
|
|
27
29
|
`)
|
|
28
30
|
.action((_opts, cmd) => __awaiter(void 0, void 0, void 0, function* () {
|
|
29
31
|
var _a, _b, _c, _d;
|
|
@@ -31,7 +33,9 @@ Examples:
|
|
|
31
33
|
const globalConfig = (0, index_config_1.readGlobalConfig)();
|
|
32
34
|
const paths = (0, project_root_1.ensureProjectPaths)(process.cwd());
|
|
33
35
|
const indexConfig = (0, index_config_1.readIndexConfig)(paths.configPath);
|
|
34
|
-
const hasUpdates = options.embedMode !== undefined ||
|
|
36
|
+
const hasUpdates = options.embedMode !== undefined ||
|
|
37
|
+
options.modelTier !== undefined ||
|
|
38
|
+
options.queryLog !== undefined;
|
|
35
39
|
if (!hasUpdates) {
|
|
36
40
|
// Show current config
|
|
37
41
|
const tier = (_a = config_1.MODEL_TIERS[globalConfig.modelTier]) !== null && _a !== void 0 ? _a : config_1.MODEL_TIERS.small;
|
|
@@ -39,10 +43,11 @@ Examples:
|
|
|
39
43
|
console.log(` Model tier: ${globalConfig.modelTier} (${tier.vectorDim}d, ${tier.params})`);
|
|
40
44
|
console.log(` Embed mode: ${globalConfig.embedMode}`);
|
|
41
45
|
console.log(` Embed model: ${globalConfig.embedMode === "gpu" ? tier.mlxModel : tier.onnxModel}`);
|
|
46
|
+
console.log(` Query log: ${globalConfig.queryLog ? "on" : "off"}`);
|
|
42
47
|
if (indexConfig === null || indexConfig === void 0 ? void 0 : indexConfig.indexedAt) {
|
|
43
48
|
console.log(` Last indexed: ${indexConfig.indexedAt}`);
|
|
44
49
|
}
|
|
45
|
-
console.log(`\nTo change: gmax config --embed-mode <cpu|gpu> --model-tier <small|standard>`);
|
|
50
|
+
console.log(`\nTo change: gmax config --embed-mode <cpu|gpu> --model-tier <small|standard> --query-log <on|off>`);
|
|
46
51
|
yield (0, exit_1.gracefulExit)();
|
|
47
52
|
return;
|
|
48
53
|
}
|
|
@@ -57,6 +62,24 @@ Examples:
|
|
|
57
62
|
yield (0, exit_1.gracefulExit)(1);
|
|
58
63
|
return;
|
|
59
64
|
}
|
|
65
|
+
// Handle query-log toggle (independent of model/embed changes)
|
|
66
|
+
if (options.queryLog !== undefined) {
|
|
67
|
+
if (!["on", "off"].includes(options.queryLog)) {
|
|
68
|
+
console.error(`Invalid query-log value: ${options.queryLog} (use on or off)`);
|
|
69
|
+
yield (0, exit_1.gracefulExit)(1);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const enabled = options.queryLog === "on";
|
|
73
|
+
(0, index_config_1.writeGlobalConfig)(Object.assign(Object.assign({}, globalConfig), { queryLog: enabled }));
|
|
74
|
+
console.log(`Query logging ${enabled ? "enabled" : "disabled"}. Logs at ~/.gmax/logs/queries.jsonl`);
|
|
75
|
+
// If only query-log was changed, skip model updates
|
|
76
|
+
if (!options.embedMode && !options.modelTier) {
|
|
77
|
+
yield (0, exit_1.gracefulExit)();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// Reload config after queryLog write
|
|
81
|
+
Object.assign(globalConfig, (0, index_config_1.readGlobalConfig)());
|
|
82
|
+
}
|
|
60
83
|
const newTier = (_b = options.modelTier) !== null && _b !== void 0 ? _b : globalConfig.modelTier;
|
|
61
84
|
const newMode = (_c = options.embedMode) !== null && _c !== void 0 ? _c : globalConfig.embedMode;
|
|
62
85
|
const tier = (_d = config_1.MODEL_TIERS[newTier]) !== null && _d !== void 0 ? _d : config_1.MODEL_TIERS.small;
|
|
@@ -66,6 +89,7 @@ Examples:
|
|
|
66
89
|
vectorDim: tier.vectorDim,
|
|
67
90
|
embedMode: newMode,
|
|
68
91
|
mlxModel: newMode === "gpu" ? tier.mlxModel : undefined,
|
|
92
|
+
queryLog: globalConfig.queryLog,
|
|
69
93
|
});
|
|
70
94
|
(0, index_config_1.writeSetupConfig)(paths.configPath, {
|
|
71
95
|
embedMode: newMode,
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.extract = void 0;
|
|
46
|
+
const fs = __importStar(require("node:fs"));
|
|
47
|
+
const path = __importStar(require("node:path"));
|
|
48
|
+
const commander_1 = require("commander");
|
|
49
|
+
const vector_db_1 = require("../lib/store/vector-db");
|
|
50
|
+
const exit_1 = require("../lib/utils/exit");
|
|
51
|
+
const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
52
|
+
const import_extractor_1 = require("../lib/utils/import-extractor");
|
|
53
|
+
const project_root_1 = require("../lib/utils/project-root");
|
|
54
|
+
const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
55
|
+
const style = {
|
|
56
|
+
bold: (s) => (useColors ? `\x1b[1m${s}\x1b[22m` : s),
|
|
57
|
+
dim: (s) => (useColors ? `\x1b[2m${s}\x1b[22m` : s),
|
|
58
|
+
green: (s) => (useColors ? `\x1b[32m${s}\x1b[39m` : s),
|
|
59
|
+
cyan: (s) => (useColors ? `\x1b[36m${s}\x1b[39m` : s),
|
|
60
|
+
};
|
|
61
|
+
const ROLE_PRIORITY = {
|
|
62
|
+
ORCHESTRATION: 3,
|
|
63
|
+
DEFINITION: 2,
|
|
64
|
+
IMPLEMENTATION: 1,
|
|
65
|
+
};
|
|
66
|
+
function findSymbolChunks(symbol, db, projectRoot) {
|
|
67
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
68
|
+
const table = yield db.ensureTable();
|
|
69
|
+
const prefix = projectRoot.endsWith("/") ? projectRoot : `${projectRoot}/`;
|
|
70
|
+
const rows = yield table
|
|
71
|
+
.query()
|
|
72
|
+
.select([
|
|
73
|
+
"path",
|
|
74
|
+
"start_line",
|
|
75
|
+
"end_line",
|
|
76
|
+
"role",
|
|
77
|
+
"is_exported",
|
|
78
|
+
"defined_symbols",
|
|
79
|
+
])
|
|
80
|
+
.where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbol)}') AND path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`)
|
|
81
|
+
.limit(10)
|
|
82
|
+
.toArray();
|
|
83
|
+
return rows.map((row) => ({
|
|
84
|
+
path: String(row.path || ""),
|
|
85
|
+
startLine: Number(row.start_line || 0),
|
|
86
|
+
endLine: Number(row.end_line || 0),
|
|
87
|
+
role: String(row.role || "IMPLEMENTATION"),
|
|
88
|
+
exported: Boolean(row.is_exported),
|
|
89
|
+
definedSymbols: Array.isArray(row.defined_symbols)
|
|
90
|
+
? row.defined_symbols
|
|
91
|
+
: [],
|
|
92
|
+
}));
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function pickBestMatch(chunks, symbol) {
|
|
96
|
+
// Prefer chunks where the symbol is first in defined_symbols, then by role priority
|
|
97
|
+
return chunks.sort((a, b) => {
|
|
98
|
+
const aFirst = a.definedSymbols[0] === symbol ? 1 : 0;
|
|
99
|
+
const bFirst = b.definedSymbols[0] === symbol ? 1 : 0;
|
|
100
|
+
if (bFirst !== aFirst)
|
|
101
|
+
return bFirst - aFirst;
|
|
102
|
+
return (ROLE_PRIORITY[b.role] || 0) - (ROLE_PRIORITY[a.role] || 0);
|
|
103
|
+
})[0];
|
|
104
|
+
}
|
|
105
|
+
exports.extract = new commander_1.Command("extract")
|
|
106
|
+
.description("Extract full function/class body by symbol name")
|
|
107
|
+
.argument("<symbol>", "The symbol to extract")
|
|
108
|
+
.option("--root <dir>", "Project root directory")
|
|
109
|
+
.option("--agent", "Compact output for AI agents", false)
|
|
110
|
+
.option("--imports", "Prepend file imports", false)
|
|
111
|
+
.action((symbol, opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
112
|
+
var _a;
|
|
113
|
+
let vectorDb = null;
|
|
114
|
+
const root = opts.root ? path.resolve(opts.root) : process.cwd();
|
|
115
|
+
try {
|
|
116
|
+
const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
|
|
117
|
+
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
118
|
+
vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
|
|
119
|
+
const chunks = yield findSymbolChunks(symbol, vectorDb, projectRoot);
|
|
120
|
+
if (chunks.length === 0) {
|
|
121
|
+
const lines = [
|
|
122
|
+
`Symbol not found: ${opts.agent ? symbol : style.bold(symbol)}`,
|
|
123
|
+
];
|
|
124
|
+
if (!opts.agent) {
|
|
125
|
+
lines.push("", style.dim("Possible reasons:"), style.dim(" \u2022 The symbol doesn't exist in any indexed project"), style.dim(" \u2022 The containing file hasn't been indexed yet"), style.dim(" \u2022 The name is spelled differently in the source"), "", style.dim("Try:"), style.dim(" gmax status \u2014 see which projects are indexed"), style.dim(" gmax search <name> \u2014 fuzzy search for similar symbols"));
|
|
126
|
+
}
|
|
127
|
+
console.log(lines.join("\n"));
|
|
128
|
+
process.exitCode = 1;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const best = pickBestMatch(chunks, symbol);
|
|
132
|
+
const content = fs.readFileSync(best.path, "utf-8");
|
|
133
|
+
const allLines = content.split("\n");
|
|
134
|
+
const startLine = best.startLine; // 0-based
|
|
135
|
+
const endLine = Math.min(best.endLine, allLines.length - 1);
|
|
136
|
+
const body = allLines.slice(startLine, endLine + 1);
|
|
137
|
+
const relPath = best.path.startsWith(projectRoot)
|
|
138
|
+
? best.path.slice(projectRoot.length + 1)
|
|
139
|
+
: best.path;
|
|
140
|
+
if (opts.agent) {
|
|
141
|
+
// Compact: path:start-end header then raw code
|
|
142
|
+
if (opts.imports) {
|
|
143
|
+
const imports = (0, import_extractor_1.extractImportsFromContent)(content);
|
|
144
|
+
if (imports)
|
|
145
|
+
console.log(imports);
|
|
146
|
+
}
|
|
147
|
+
console.log(`${relPath}:${startLine + 1}-${endLine + 1}`);
|
|
148
|
+
console.log(body.join("\n"));
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// Rich output with line numbers
|
|
152
|
+
if (opts.imports) {
|
|
153
|
+
const imports = (0, import_extractor_1.extractImportsFromContent)(content);
|
|
154
|
+
if (imports) {
|
|
155
|
+
console.log(style.dim(imports));
|
|
156
|
+
console.log();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const exportedStr = best.exported ? ", exported" : "";
|
|
160
|
+
console.log(style.dim(`// ${relPath}:${startLine + 1}-${endLine + 1} [${best.role}${exportedStr}]`));
|
|
161
|
+
const lineNumWidth = String(endLine + 1).length;
|
|
162
|
+
for (let i = 0; i < body.length; i++) {
|
|
163
|
+
const lineNum = String(startLine + 1 + i).padStart(lineNumWidth);
|
|
164
|
+
console.log(`${style.dim(`${lineNum}\u2502`)} ${body[i]}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Show other definitions if symbol exists in multiple files
|
|
168
|
+
const others = chunks.filter((c) => c !== best).slice(0, 3);
|
|
169
|
+
if (others.length > 0 && !opts.agent) {
|
|
170
|
+
const otherLocs = others
|
|
171
|
+
.map((c) => {
|
|
172
|
+
const r = c.path.startsWith(projectRoot)
|
|
173
|
+
? c.path.slice(projectRoot.length + 1)
|
|
174
|
+
: c.path;
|
|
175
|
+
return `${r}:${c.startLine + 1}`;
|
|
176
|
+
})
|
|
177
|
+
.join(", ");
|
|
178
|
+
console.log(`\n${style.dim(`Also defined in: ${otherLocs}`)}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
183
|
+
console.error("Extract failed:", message);
|
|
184
|
+
process.exitCode = 1;
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
if (vectorDb) {
|
|
188
|
+
try {
|
|
189
|
+
yield vectorDb.close();
|
|
190
|
+
}
|
|
191
|
+
catch (_b) { }
|
|
192
|
+
}
|
|
193
|
+
yield (0, exit_1.gracefulExit)();
|
|
194
|
+
}
|
|
195
|
+
}));
|
package/dist/commands/mcp.js
CHANGED
|
@@ -136,6 +136,32 @@ const TOOLS = [
|
|
|
136
136
|
required: ["symbol"],
|
|
137
137
|
},
|
|
138
138
|
},
|
|
139
|
+
{
|
|
140
|
+
name: "extract_symbol",
|
|
141
|
+
description: "Extract complete function/class body by symbol name.",
|
|
142
|
+
inputSchema: {
|
|
143
|
+
type: "object",
|
|
144
|
+
properties: {
|
|
145
|
+
symbol: { type: "string", description: "Symbol name to extract" },
|
|
146
|
+
root: { type: "string", description: "Project root (absolute path)" },
|
|
147
|
+
include_imports: { type: "boolean", description: "Prepend file imports" },
|
|
148
|
+
},
|
|
149
|
+
required: ["symbol"],
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: "peek_symbol",
|
|
154
|
+
description: "Compact symbol overview: signature + callers + callees.",
|
|
155
|
+
inputSchema: {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties: {
|
|
158
|
+
symbol: { type: "string", description: "Symbol name" },
|
|
159
|
+
root: { type: "string", description: "Project root (absolute path)" },
|
|
160
|
+
depth: { type: "number", description: "Caller depth (default 1, max 3)" },
|
|
161
|
+
},
|
|
162
|
+
required: ["symbol"],
|
|
163
|
+
},
|
|
164
|
+
},
|
|
139
165
|
{
|
|
140
166
|
name: "list_symbols",
|
|
141
167
|
description: "List indexed symbols with role and export status.",
|
|
@@ -441,7 +467,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
441
467
|
}
|
|
442
468
|
const result = yield searcher.search(query, limit, { rerank: true }, Object.keys(filters).length > 0 ? filters : undefined, pathPrefix);
|
|
443
469
|
if (!result.data || result.data.length === 0) {
|
|
444
|
-
return ok("No matches found.");
|
|
470
|
+
return ok("No matches found. Try broadening your query, using fewer keywords, or check `gmax status` to verify the project is indexed.");
|
|
445
471
|
}
|
|
446
472
|
const minScore = typeof args.min_score === "number" ? args.min_score : 0;
|
|
447
473
|
const maxPerFile = typeof args.max_per_file === "number" ? args.max_per_file : 0;
|
|
@@ -743,7 +769,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
743
769
|
const depth = Math.min(Math.max(Number(args.depth) || 1, 1), 3);
|
|
744
770
|
const graph = yield builder.buildGraphMultiHop(symbol, depth);
|
|
745
771
|
if (!graph.center) {
|
|
746
|
-
return ok(`Symbol '${symbol}' not found in the index.`);
|
|
772
|
+
return ok(`Symbol '${symbol}' not found in the index. Check \`gmax status\` to see which projects are indexed, or try \`gmax search ${symbol}\` to find similar symbols.`);
|
|
747
773
|
}
|
|
748
774
|
const lines = [];
|
|
749
775
|
// Center
|
|
@@ -810,6 +836,226 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
810
836
|
}
|
|
811
837
|
});
|
|
812
838
|
}
|
|
839
|
+
function handleExtractSymbol(args) {
|
|
840
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
841
|
+
ensureWatcher();
|
|
842
|
+
const symbol = String(args.symbol || "");
|
|
843
|
+
if (!symbol)
|
|
844
|
+
return err("Missing required parameter: symbol");
|
|
845
|
+
try {
|
|
846
|
+
const root = typeof args.root === "string" && args.root
|
|
847
|
+
? args.root
|
|
848
|
+
: projectRoot;
|
|
849
|
+
const db = getVectorDb();
|
|
850
|
+
const table = yield db.ensureTable();
|
|
851
|
+
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
852
|
+
const rows = yield table
|
|
853
|
+
.query()
|
|
854
|
+
.select([
|
|
855
|
+
"path",
|
|
856
|
+
"start_line",
|
|
857
|
+
"end_line",
|
|
858
|
+
"role",
|
|
859
|
+
"is_exported",
|
|
860
|
+
"defined_symbols",
|
|
861
|
+
])
|
|
862
|
+
.where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbol)}') AND path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`)
|
|
863
|
+
.limit(10)
|
|
864
|
+
.toArray();
|
|
865
|
+
if (rows.length === 0) {
|
|
866
|
+
return ok(`Symbol '${symbol}' not found in the index. Check \`gmax status\` to see which projects are indexed, or try \`gmax search ${symbol}\` to find similar symbols.`);
|
|
867
|
+
}
|
|
868
|
+
// Pick best match: prefer exact first-defined, then highest role
|
|
869
|
+
const ROLE_PRI = {
|
|
870
|
+
ORCHESTRATION: 3,
|
|
871
|
+
DEFINITION: 2,
|
|
872
|
+
IMPLEMENTATION: 1,
|
|
873
|
+
};
|
|
874
|
+
const sorted = rows.sort((a, b) => {
|
|
875
|
+
const aDefs = Array.isArray(a.defined_symbols)
|
|
876
|
+
? a.defined_symbols
|
|
877
|
+
: [];
|
|
878
|
+
const bDefs = Array.isArray(b.defined_symbols)
|
|
879
|
+
? b.defined_symbols
|
|
880
|
+
: [];
|
|
881
|
+
const aFirst = aDefs[0] === symbol ? 1 : 0;
|
|
882
|
+
const bFirst = bDefs[0] === symbol ? 1 : 0;
|
|
883
|
+
if (bFirst !== aFirst)
|
|
884
|
+
return bFirst - aFirst;
|
|
885
|
+
return ((ROLE_PRI[String(b.role)] || 0) - (ROLE_PRI[String(a.role)] || 0));
|
|
886
|
+
});
|
|
887
|
+
const best = sorted[0];
|
|
888
|
+
const filePath = String(best.path);
|
|
889
|
+
const startLine = Number(best.start_line || 0);
|
|
890
|
+
const endLine = Number(best.end_line || 0);
|
|
891
|
+
const role = String(best.role || "IMPLEMENTATION");
|
|
892
|
+
const exported = Boolean(best.is_exported);
|
|
893
|
+
const fs = yield Promise.resolve().then(() => __importStar(require("node:fs")));
|
|
894
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
895
|
+
const allLines = content.split("\n");
|
|
896
|
+
const body = allLines
|
|
897
|
+
.slice(startLine, Math.min(endLine + 1, allLines.length))
|
|
898
|
+
.join("\n");
|
|
899
|
+
const relPath = filePath.startsWith(root)
|
|
900
|
+
? filePath.slice(root.length + 1)
|
|
901
|
+
: filePath;
|
|
902
|
+
const exportedStr = exported ? ", exported" : "";
|
|
903
|
+
const parts = [];
|
|
904
|
+
if (args.include_imports) {
|
|
905
|
+
const { extractImportsFromContent } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/import-extractor")));
|
|
906
|
+
const imports = extractImportsFromContent(content);
|
|
907
|
+
if (imports)
|
|
908
|
+
parts.push(imports, "");
|
|
909
|
+
}
|
|
910
|
+
parts.push(`// ${relPath}:${startLine + 1}-${endLine + 1} [${role}${exportedStr}]`);
|
|
911
|
+
parts.push(body);
|
|
912
|
+
// Note other definitions
|
|
913
|
+
const others = sorted.slice(1, 4);
|
|
914
|
+
if (others.length > 0) {
|
|
915
|
+
const otherLocs = others
|
|
916
|
+
.map((r) => {
|
|
917
|
+
const p = String(r.path);
|
|
918
|
+
const rel = p.startsWith(root) ? p.slice(root.length + 1) : p;
|
|
919
|
+
return `${rel}:${Number(r.start_line || 0) + 1}`;
|
|
920
|
+
})
|
|
921
|
+
.join(", ");
|
|
922
|
+
parts.push("", `Also defined in: ${otherLocs}`);
|
|
923
|
+
}
|
|
924
|
+
return ok(parts.join("\n"));
|
|
925
|
+
}
|
|
926
|
+
catch (e) {
|
|
927
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
928
|
+
return err(`Extract failed: ${msg}`);
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
function handlePeekSymbol(args) {
|
|
933
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
934
|
+
ensureWatcher();
|
|
935
|
+
const symbol = String(args.symbol || "");
|
|
936
|
+
if (!symbol)
|
|
937
|
+
return err("Missing required parameter: symbol");
|
|
938
|
+
try {
|
|
939
|
+
const root = typeof args.root === "string" && args.root
|
|
940
|
+
? args.root
|
|
941
|
+
: projectRoot;
|
|
942
|
+
const depth = Math.min(Math.max(Number(args.depth || 1), 1), 3);
|
|
943
|
+
const db = getVectorDb();
|
|
944
|
+
const { GraphBuilder } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/graph-builder")));
|
|
945
|
+
const builder = new GraphBuilder(db, root);
|
|
946
|
+
const graph = yield builder.buildGraph(symbol);
|
|
947
|
+
if (!graph.center) {
|
|
948
|
+
return ok(`Symbol '${symbol}' not found in the index. Check \`gmax status\` to see which projects are indexed, or try \`gmax search ${symbol}\` to find similar symbols.`);
|
|
949
|
+
}
|
|
950
|
+
const center = graph.center;
|
|
951
|
+
const rel = (p) => p.startsWith(root) ? p.slice(root.length + 1) : p;
|
|
952
|
+
// Get chunk metadata for is_exported and end_line
|
|
953
|
+
const table = yield db.ensureTable();
|
|
954
|
+
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
955
|
+
const metaRows = yield table
|
|
956
|
+
.query()
|
|
957
|
+
.select(["is_exported", "start_line", "end_line"])
|
|
958
|
+
.where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbol)}') AND path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`)
|
|
959
|
+
.limit(1)
|
|
960
|
+
.toArray();
|
|
961
|
+
const exported = metaRows.length > 0 && Boolean(metaRows[0].is_exported);
|
|
962
|
+
const startLine = metaRows.length > 0
|
|
963
|
+
? Number(metaRows[0].start_line || 0)
|
|
964
|
+
: center.line;
|
|
965
|
+
const endLine = metaRows.length > 0
|
|
966
|
+
? Number(metaRows[0].end_line || 0)
|
|
967
|
+
: center.line;
|
|
968
|
+
// Get signature from source
|
|
969
|
+
const fs = yield Promise.resolve().then(() => __importStar(require("node:fs")));
|
|
970
|
+
let sigText = "(source not available)";
|
|
971
|
+
let bodyLines = 0;
|
|
972
|
+
try {
|
|
973
|
+
const content = fs.readFileSync(center.file, "utf-8");
|
|
974
|
+
const lines = content.split("\n");
|
|
975
|
+
const chunk = lines.slice(startLine, endLine + 1);
|
|
976
|
+
bodyLines = chunk.length;
|
|
977
|
+
const sigLines = [];
|
|
978
|
+
for (const line of chunk) {
|
|
979
|
+
sigLines.push(line);
|
|
980
|
+
if (line.includes("{") || line.includes("=>"))
|
|
981
|
+
break;
|
|
982
|
+
}
|
|
983
|
+
sigText = sigLines.join("\n").trim();
|
|
984
|
+
}
|
|
985
|
+
catch (_a) { }
|
|
986
|
+
// Get callers
|
|
987
|
+
let callerList;
|
|
988
|
+
if (depth > 1) {
|
|
989
|
+
const multiHop = yield builder.buildGraphMultiHop(symbol, depth);
|
|
990
|
+
const flat = [];
|
|
991
|
+
function walkCallers(tree) {
|
|
992
|
+
for (const t of tree) {
|
|
993
|
+
flat.push({
|
|
994
|
+
symbol: t.node.symbol,
|
|
995
|
+
file: t.node.file,
|
|
996
|
+
line: t.node.line,
|
|
997
|
+
});
|
|
998
|
+
walkCallers(t.callers);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
walkCallers(multiHop.callerTree);
|
|
1002
|
+
callerList = flat;
|
|
1003
|
+
}
|
|
1004
|
+
else {
|
|
1005
|
+
callerList = graph.callers;
|
|
1006
|
+
}
|
|
1007
|
+
const exportedStr = exported ? ", exported" : "";
|
|
1008
|
+
const parts = [];
|
|
1009
|
+
parts.push(`${center.symbol} ${rel(center.file)}:${center.line + 1} [${center.role}${exportedStr}]`);
|
|
1010
|
+
parts.push("");
|
|
1011
|
+
parts.push(` ${sigText}`);
|
|
1012
|
+
if (bodyLines > 3) {
|
|
1013
|
+
parts.push(` // ... (${bodyLines} lines total)`);
|
|
1014
|
+
}
|
|
1015
|
+
parts.push("");
|
|
1016
|
+
// Callers
|
|
1017
|
+
const maxCallers = 5;
|
|
1018
|
+
if (callerList.length > 0) {
|
|
1019
|
+
parts.push(`callers (${callerList.length}):`);
|
|
1020
|
+
for (const c of callerList.slice(0, maxCallers)) {
|
|
1021
|
+
const loc = c.file
|
|
1022
|
+
? `${rel(c.file)}:${c.line + 1}`
|
|
1023
|
+
: "(not indexed)";
|
|
1024
|
+
parts.push(` <- ${c.symbol} ${loc}`);
|
|
1025
|
+
}
|
|
1026
|
+
if (callerList.length > maxCallers) {
|
|
1027
|
+
parts.push(` ... and ${callerList.length - maxCallers} more`);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
else {
|
|
1031
|
+
parts.push("No known callers.");
|
|
1032
|
+
}
|
|
1033
|
+
parts.push("");
|
|
1034
|
+
// Callees
|
|
1035
|
+
const maxCallees = 8;
|
|
1036
|
+
if (graph.callees.length > 0) {
|
|
1037
|
+
parts.push(`callees (${graph.callees.length}):`);
|
|
1038
|
+
for (const c of graph.callees.slice(0, maxCallees)) {
|
|
1039
|
+
const loc = c.file
|
|
1040
|
+
? `${rel(c.file)}:${c.line + 1}`
|
|
1041
|
+
: "(not indexed)";
|
|
1042
|
+
parts.push(` -> ${c.symbol} ${loc}`);
|
|
1043
|
+
}
|
|
1044
|
+
if (graph.callees.length > maxCallees) {
|
|
1045
|
+
parts.push(` ... and ${graph.callees.length - maxCallees} more`);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
else {
|
|
1049
|
+
parts.push("No known callees.");
|
|
1050
|
+
}
|
|
1051
|
+
return ok(parts.join("\n"));
|
|
1052
|
+
}
|
|
1053
|
+
catch (e) {
|
|
1054
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1055
|
+
return err(`Peek failed: ${msg}`);
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
813
1059
|
function handleListSymbols(args) {
|
|
814
1060
|
return __awaiter(this, void 0, void 0, function* () {
|
|
815
1061
|
ensureWatcher();
|
|
@@ -870,7 +1116,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
870
1116
|
})
|
|
871
1117
|
.slice(0, limit);
|
|
872
1118
|
if (entries.length === 0) {
|
|
873
|
-
return ok("No symbols found. Run
|
|
1119
|
+
return ok("No symbols found. Run `gmax status` to verify the project is indexed, or `gmax index` to rebuild.");
|
|
874
1120
|
}
|
|
875
1121
|
const lines = entries.map((e) => {
|
|
876
1122
|
const rel = e.path.startsWith(projectRoot)
|
|
@@ -1118,7 +1364,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1118
1364
|
.where(`path = '${(0, filter_builder_1.escapeSqlString)(absPath)}'`)
|
|
1119
1365
|
.toArray();
|
|
1120
1366
|
if (fileChunks.length === 0) {
|
|
1121
|
-
return ok(`File not found in index: ${file}
|
|
1367
|
+
return ok(`File not found in index: ${file}. Check that the path is relative to the project root. Run \`gmax status\` to see indexed projects.`);
|
|
1122
1368
|
}
|
|
1123
1369
|
const definedHere = new Set();
|
|
1124
1370
|
const referencedHere = new Set();
|
|
@@ -1236,7 +1482,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1236
1482
|
files.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1237
1483
|
const top = files.slice(0, limit);
|
|
1238
1484
|
if (top.length === 0) {
|
|
1239
|
-
return ok(`No indexed files found for ${root}
|
|
1485
|
+
return ok(`No indexed files found for ${root}. Run \`gmax add\` to register and index this project.`);
|
|
1240
1486
|
}
|
|
1241
1487
|
const now = Date.now();
|
|
1242
1488
|
const lines = [
|
|
@@ -1277,32 +1523,69 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1277
1523
|
return { tools: TOOLS };
|
|
1278
1524
|
}));
|
|
1279
1525
|
server.setRequestHandler(types_js_1.CallToolRequestSchema, (request) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1526
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1280
1527
|
const { name, arguments: args } = request.params;
|
|
1281
1528
|
const toolArgs = (args !== null && args !== void 0 ? args : {});
|
|
1529
|
+
const startMs = Date.now();
|
|
1530
|
+
let result;
|
|
1282
1531
|
switch (name) {
|
|
1283
1532
|
case "semantic_search":
|
|
1284
|
-
|
|
1533
|
+
result = yield handleSemanticSearch(toolArgs, false);
|
|
1534
|
+
break;
|
|
1285
1535
|
case "search_all":
|
|
1286
|
-
|
|
1536
|
+
result = yield handleSemanticSearch(toolArgs, true);
|
|
1537
|
+
break;
|
|
1287
1538
|
case "code_skeleton":
|
|
1288
|
-
|
|
1539
|
+
result = yield handleCodeSkeleton(toolArgs);
|
|
1540
|
+
break;
|
|
1289
1541
|
case "trace_calls":
|
|
1290
|
-
|
|
1542
|
+
result = yield handleTraceCalls(toolArgs);
|
|
1543
|
+
break;
|
|
1544
|
+
case "extract_symbol":
|
|
1545
|
+
result = yield handleExtractSymbol(toolArgs);
|
|
1546
|
+
break;
|
|
1547
|
+
case "peek_symbol":
|
|
1548
|
+
result = yield handlePeekSymbol(toolArgs);
|
|
1549
|
+
break;
|
|
1291
1550
|
case "list_symbols":
|
|
1292
|
-
|
|
1551
|
+
result = yield handleListSymbols(toolArgs);
|
|
1552
|
+
break;
|
|
1293
1553
|
case "index_status":
|
|
1294
|
-
|
|
1554
|
+
result = yield handleIndexStatus();
|
|
1555
|
+
break;
|
|
1295
1556
|
case "summarize_directory":
|
|
1296
|
-
|
|
1557
|
+
result = yield handleSummarizeDirectory(toolArgs);
|
|
1558
|
+
break;
|
|
1297
1559
|
case "summarize_project":
|
|
1298
|
-
|
|
1560
|
+
result = yield handleSummarizeProject(toolArgs);
|
|
1561
|
+
break;
|
|
1299
1562
|
case "related_files":
|
|
1300
|
-
|
|
1563
|
+
result = yield handleRelatedFiles(toolArgs);
|
|
1564
|
+
break;
|
|
1301
1565
|
case "recent_changes":
|
|
1302
|
-
|
|
1566
|
+
result = yield handleRecentChanges(toolArgs);
|
|
1567
|
+
break;
|
|
1303
1568
|
default:
|
|
1304
1569
|
return err(`Unknown tool: ${name}`);
|
|
1305
1570
|
}
|
|
1571
|
+
// Best-effort query logging
|
|
1572
|
+
try {
|
|
1573
|
+
const { logQuery } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/query-log")));
|
|
1574
|
+
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 : "";
|
|
1575
|
+
const resultLines = text.split("\n").filter((l) => l.trim()).length;
|
|
1576
|
+
logQuery({
|
|
1577
|
+
ts: new Date().toISOString(),
|
|
1578
|
+
source: "mcp",
|
|
1579
|
+
tool: name,
|
|
1580
|
+
query: String((_f = (_e = (_d = toolArgs.query) !== null && _d !== void 0 ? _d : toolArgs.symbol) !== null && _e !== void 0 ? _e : toolArgs.target) !== null && _f !== void 0 ? _f : ""),
|
|
1581
|
+
project: projectRoot,
|
|
1582
|
+
results: resultLines,
|
|
1583
|
+
ms: Date.now() - startMs,
|
|
1584
|
+
error: result.isError ? text.slice(0, 200) : undefined,
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
catch (_g) { }
|
|
1588
|
+
return result;
|
|
1306
1589
|
}));
|
|
1307
1590
|
yield server.connect(transport);
|
|
1308
1591
|
// Kick off index readiness check and watcher in background
|