grepmax 0.9.5 → 0.10.1

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.
@@ -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 || options.modelTier !== 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
+ }));
@@ -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.",
@@ -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();
@@ -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
- return handleSemanticSearch(toolArgs, false);
1533
+ result = yield handleSemanticSearch(toolArgs, false);
1534
+ break;
1285
1535
  case "search_all":
1286
- return handleSemanticSearch(toolArgs, true);
1536
+ result = yield handleSemanticSearch(toolArgs, true);
1537
+ break;
1287
1538
  case "code_skeleton":
1288
- return handleCodeSkeleton(toolArgs);
1539
+ result = yield handleCodeSkeleton(toolArgs);
1540
+ break;
1289
1541
  case "trace_calls":
1290
- return handleTraceCalls(toolArgs);
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
- return handleListSymbols(toolArgs);
1551
+ result = yield handleListSymbols(toolArgs);
1552
+ break;
1293
1553
  case "index_status":
1294
- return handleIndexStatus();
1554
+ result = yield handleIndexStatus();
1555
+ break;
1295
1556
  case "summarize_directory":
1296
- return handleSummarizeDirectory(toolArgs);
1557
+ result = yield handleSummarizeDirectory(toolArgs);
1558
+ break;
1297
1559
  case "summarize_project":
1298
- return handleSummarizeProject(toolArgs);
1560
+ result = yield handleSummarizeProject(toolArgs);
1561
+ break;
1299
1562
  case "related_files":
1300
- return handleRelatedFiles(toolArgs);
1563
+ result = yield handleRelatedFiles(toolArgs);
1564
+ break;
1301
1565
  case "recent_changes":
1302
- return handleRecentChanges(toolArgs);
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