grepmax 0.7.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.
Potentially problematic release.
This version of grepmax might be problematic. Click here for more details.
- package/LICENSE +202 -0
- package/NOTICE +33 -0
- package/README.md +375 -0
- package/dist/commands/claude-code.js +60 -0
- package/dist/commands/codex.js +98 -0
- package/dist/commands/doctor.js +92 -0
- package/dist/commands/droid.js +189 -0
- package/dist/commands/index.js +125 -0
- package/dist/commands/list.js +120 -0
- package/dist/commands/mcp.js +572 -0
- package/dist/commands/opencode.js +199 -0
- package/dist/commands/search.js +539 -0
- package/dist/commands/serve.js +512 -0
- package/dist/commands/setup.js +162 -0
- package/dist/commands/skeleton.js +288 -0
- package/dist/commands/symbols.js +129 -0
- package/dist/commands/trace.js +50 -0
- package/dist/commands/verify.js +174 -0
- package/dist/config.js +120 -0
- package/dist/eval.js +618 -0
- package/dist/index.js +82 -0
- package/dist/lib/core/languages.js +237 -0
- package/dist/lib/graph/graph-builder.js +105 -0
- package/dist/lib/index/chunker.js +663 -0
- package/dist/lib/index/grammar-loader.js +110 -0
- package/dist/lib/index/ignore-patterns.js +63 -0
- package/dist/lib/index/index-config.js +86 -0
- package/dist/lib/index/sync-helpers.js +97 -0
- package/dist/lib/index/syncer.js +396 -0
- package/dist/lib/index/walker.js +164 -0
- package/dist/lib/index/watcher.js +245 -0
- package/dist/lib/output/formatter.js +161 -0
- package/dist/lib/output/json-formatter.js +6 -0
- package/dist/lib/search/intent.js +23 -0
- package/dist/lib/search/searcher.js +475 -0
- package/dist/lib/setup/model-loader.js +107 -0
- package/dist/lib/setup/setup-helpers.js +106 -0
- package/dist/lib/skeleton/body-fields.js +175 -0
- package/dist/lib/skeleton/index.js +24 -0
- package/dist/lib/skeleton/retriever.js +36 -0
- package/dist/lib/skeleton/skeletonizer.js +483 -0
- package/dist/lib/skeleton/summary-formatter.js +90 -0
- package/dist/lib/store/meta-cache.js +143 -0
- package/dist/lib/store/types.js +2 -0
- package/dist/lib/store/vector-db.js +340 -0
- package/dist/lib/utils/cleanup.js +33 -0
- package/dist/lib/utils/exit.js +38 -0
- package/dist/lib/utils/file-utils.js +131 -0
- package/dist/lib/utils/filter-builder.js +17 -0
- package/dist/lib/utils/formatter.js +230 -0
- package/dist/lib/utils/git.js +83 -0
- package/dist/lib/utils/lock.js +157 -0
- package/dist/lib/utils/project-root.js +107 -0
- package/dist/lib/utils/server-registry.js +97 -0
- package/dist/lib/workers/colbert-math.js +107 -0
- package/dist/lib/workers/colbert-tokenizer.js +113 -0
- package/dist/lib/workers/download-worker.js +169 -0
- package/dist/lib/workers/embeddings/colbert.js +213 -0
- package/dist/lib/workers/embeddings/granite.js +180 -0
- package/dist/lib/workers/embeddings/mlx-client.js +144 -0
- package/dist/lib/workers/orchestrator.js +350 -0
- package/dist/lib/workers/pool.js +373 -0
- package/dist/lib/workers/process-child.js +92 -0
- package/dist/lib/workers/worker.js +31 -0
- package/package.json +80 -0
- package/plugins/osgrep/.claude-plugin/plugin.json +20 -0
- package/plugins/osgrep/hooks/start.js +92 -0
- package/plugins/osgrep/hooks/stop.js +3 -0
- package/plugins/osgrep/hooks.json +26 -0
- package/plugins/osgrep/skills/osgrep/SKILL.md +82 -0
|
@@ -0,0 +1,572 @@
|
|
|
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.mcp = void 0;
|
|
46
|
+
exports.toStringArray = toStringArray;
|
|
47
|
+
exports.ok = ok;
|
|
48
|
+
exports.err = err;
|
|
49
|
+
const node_child_process_1 = require("node:child_process");
|
|
50
|
+
const fs = __importStar(require("node:fs"));
|
|
51
|
+
const path = __importStar(require("node:path"));
|
|
52
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
53
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
54
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
55
|
+
const commander_1 = require("commander");
|
|
56
|
+
const graph_builder_1 = require("../lib/graph/graph-builder");
|
|
57
|
+
const retriever_1 = require("../lib/skeleton/retriever");
|
|
58
|
+
const skeletonizer_1 = require("../lib/skeleton/skeletonizer");
|
|
59
|
+
const vector_db_1 = require("../lib/store/vector-db");
|
|
60
|
+
const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
61
|
+
const project_root_1 = require("../lib/utils/project-root");
|
|
62
|
+
const server_registry_1 = require("../lib/utils/server-registry");
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Tool definitions
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
const TOOLS = [
|
|
67
|
+
{
|
|
68
|
+
name: "semantic_search",
|
|
69
|
+
description: "Search code by meaning. Use natural language queries like 'where do we validate permissions' or 'how does the booking flow work'. Returns ranked code snippets with file paths, line numbers, and relevance scores.",
|
|
70
|
+
inputSchema: {
|
|
71
|
+
type: "object",
|
|
72
|
+
properties: {
|
|
73
|
+
query: {
|
|
74
|
+
type: "string",
|
|
75
|
+
description: "Natural language search query. Be specific — more words give better results.",
|
|
76
|
+
},
|
|
77
|
+
limit: {
|
|
78
|
+
type: "number",
|
|
79
|
+
description: "Max results to return (default 10, max 50)",
|
|
80
|
+
},
|
|
81
|
+
path: {
|
|
82
|
+
type: "string",
|
|
83
|
+
description: "Restrict search to files under this path prefix (e.g. 'src/auth/')",
|
|
84
|
+
},
|
|
85
|
+
min_score: {
|
|
86
|
+
type: "number",
|
|
87
|
+
description: "Minimum relevance score (0-1). Results below this threshold are filtered out. Default: 0 (no filtering)",
|
|
88
|
+
},
|
|
89
|
+
max_per_file: {
|
|
90
|
+
type: "number",
|
|
91
|
+
description: "Max results per file (default: no cap). Useful to get diversity across files.",
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
required: ["query"],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: "code_skeleton",
|
|
99
|
+
description: "Show the structure of a source file — all function/class/method signatures with bodies collapsed. Useful for understanding large files without reading every line. Returns ~4x fewer tokens than the full file.",
|
|
100
|
+
inputSchema: {
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: {
|
|
103
|
+
target: {
|
|
104
|
+
type: "string",
|
|
105
|
+
description: "File path relative to project root (e.g. 'src/services/booking.ts')",
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
required: ["target"],
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "trace_calls",
|
|
113
|
+
description: "Trace the call graph for a symbol — who calls it (callers) and what it calls (callees). Useful for understanding how functions connect across files.",
|
|
114
|
+
inputSchema: {
|
|
115
|
+
type: "object",
|
|
116
|
+
properties: {
|
|
117
|
+
symbol: {
|
|
118
|
+
type: "string",
|
|
119
|
+
description: "The function, method, or class name to trace (e.g. 'handleAuth')",
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
required: ["symbol"],
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: "list_symbols",
|
|
127
|
+
description: "List indexed symbols (functions, classes, types) with their definition locations. Useful for finding where things are defined without knowing exact names.",
|
|
128
|
+
inputSchema: {
|
|
129
|
+
type: "object",
|
|
130
|
+
properties: {
|
|
131
|
+
pattern: {
|
|
132
|
+
type: "string",
|
|
133
|
+
description: "Filter symbols by name (case-insensitive substring match)",
|
|
134
|
+
},
|
|
135
|
+
limit: {
|
|
136
|
+
type: "number",
|
|
137
|
+
description: "Max symbols to return (default 20, max 100)",
|
|
138
|
+
},
|
|
139
|
+
path: {
|
|
140
|
+
type: "string",
|
|
141
|
+
description: "Only include symbols defined under this path prefix",
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: "index_status",
|
|
148
|
+
description: "Check the status of the osgrep index and serve daemon. Returns file count, chunk count, embed mode, index age, and whether live watching is active.",
|
|
149
|
+
inputSchema: {
|
|
150
|
+
type: "object",
|
|
151
|
+
properties: {},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
];
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// Daemon lifecycle
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
let _daemonReady = null;
|
|
159
|
+
function ensureDaemon(projectRoot) {
|
|
160
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
161
|
+
const existing = (0, server_registry_1.getServerForProject)(projectRoot);
|
|
162
|
+
if (existing && (0, server_registry_1.isProcessRunning)(existing.pid)) {
|
|
163
|
+
console.log(`[MCP] Serve daemon already running (PID: ${existing.pid}, Port: ${existing.port})`);
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
console.log("[MCP] Starting serve daemon...");
|
|
167
|
+
const child = (0, node_child_process_1.spawn)("osgrep", ["serve", "-b"], {
|
|
168
|
+
cwd: projectRoot,
|
|
169
|
+
detached: true,
|
|
170
|
+
stdio: "ignore",
|
|
171
|
+
});
|
|
172
|
+
child.unref();
|
|
173
|
+
// Poll for readiness — daemon registers in ~/.osgrep/servers.json once listening
|
|
174
|
+
for (let i = 0; i < 30; i++) {
|
|
175
|
+
yield new Promise((r) => setTimeout(r, 2000));
|
|
176
|
+
const server = (0, server_registry_1.getServerForProject)(projectRoot);
|
|
177
|
+
if (server && (0, server_registry_1.isProcessRunning)(server.pid)) {
|
|
178
|
+
console.log(`[MCP] Daemon ready (PID: ${server.pid}, Port: ${server.port})`);
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
console.error("[MCP] Daemon failed to become ready within 60s");
|
|
183
|
+
return false;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// Helpers
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
function toStringArray(val) {
|
|
190
|
+
if (Array.isArray(val))
|
|
191
|
+
return val.filter((v) => typeof v === "string");
|
|
192
|
+
if (val && typeof val.toArray === "function") {
|
|
193
|
+
try {
|
|
194
|
+
const arr = val.toArray();
|
|
195
|
+
return Array.isArray(arr) ? arr.filter((v) => typeof v === "string") : [];
|
|
196
|
+
}
|
|
197
|
+
catch (_a) {
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
function ok(text) {
|
|
204
|
+
return { content: [{ type: "text", text }] };
|
|
205
|
+
}
|
|
206
|
+
function err(text) {
|
|
207
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
208
|
+
}
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// Command
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
exports.mcp = new commander_1.Command("mcp")
|
|
213
|
+
.description("Start MCP server for osgrep")
|
|
214
|
+
.action((_optsArg, _cmd) => __awaiter(void 0, void 0, void 0, function* () {
|
|
215
|
+
// --- Lifecycle ---
|
|
216
|
+
var _a;
|
|
217
|
+
let _vectorDb = null;
|
|
218
|
+
let _skeletonizer = null;
|
|
219
|
+
const cleanup = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
220
|
+
if (_vectorDb) {
|
|
221
|
+
try {
|
|
222
|
+
yield _vectorDb.close();
|
|
223
|
+
}
|
|
224
|
+
catch (_a) { }
|
|
225
|
+
_vectorDb = null;
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
const exit = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
229
|
+
yield cleanup();
|
|
230
|
+
process.exit(0);
|
|
231
|
+
});
|
|
232
|
+
process.on("SIGINT", exit);
|
|
233
|
+
process.on("SIGTERM", exit);
|
|
234
|
+
// MCP SDK doesn't handle stdin close — exit when the client disconnects
|
|
235
|
+
process.stdin.on("end", exit);
|
|
236
|
+
process.stdin.on("close", exit);
|
|
237
|
+
process.on("unhandledRejection", (reason, promise) => {
|
|
238
|
+
console.error("[ERROR] Unhandled Rejection at:", promise, "reason:", reason);
|
|
239
|
+
});
|
|
240
|
+
// MCP uses stdout — redirect all logs to stderr
|
|
241
|
+
console.log = (...args) => {
|
|
242
|
+
process.stderr.write(`[LOG] ${args.join(" ")}\n`);
|
|
243
|
+
};
|
|
244
|
+
console.error = (...args) => {
|
|
245
|
+
process.stderr.write(`[ERROR] ${args.join(" ")}\n`);
|
|
246
|
+
};
|
|
247
|
+
console.debug = (..._args) => { };
|
|
248
|
+
// --- Project context ---
|
|
249
|
+
const projectRoot = (_a = (0, project_root_1.findProjectRoot)(process.cwd())) !== null && _a !== void 0 ? _a : process.cwd();
|
|
250
|
+
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
251
|
+
// Lazy resource accessors
|
|
252
|
+
function getVectorDb() {
|
|
253
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
254
|
+
if (!_vectorDb)
|
|
255
|
+
_vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
|
|
256
|
+
return _vectorDb;
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
function getSkeletonizer() {
|
|
260
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
261
|
+
if (!_skeletonizer) {
|
|
262
|
+
_skeletonizer = new skeletonizer_1.Skeletonizer();
|
|
263
|
+
yield _skeletonizer.init();
|
|
264
|
+
}
|
|
265
|
+
return _skeletonizer;
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
// --- Tool handlers ---
|
|
269
|
+
function ensureDaemonRunning() {
|
|
270
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
271
|
+
if (_daemonReady) {
|
|
272
|
+
const ready = yield _daemonReady;
|
|
273
|
+
if (!ready)
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
const server = (0, server_registry_1.getServerForProject)(projectRoot);
|
|
277
|
+
if (server && (0, server_registry_1.isProcessRunning)(server.pid))
|
|
278
|
+
return true;
|
|
279
|
+
// Daemon died — restart it
|
|
280
|
+
console.log("[MCP] Daemon not running, restarting...");
|
|
281
|
+
_daemonReady = ensureDaemon(projectRoot);
|
|
282
|
+
return _daemonReady;
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
function handleSemanticSearch(args) {
|
|
286
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
287
|
+
const query = String(args.query || "");
|
|
288
|
+
if (!query)
|
|
289
|
+
return err("Missing required parameter: query");
|
|
290
|
+
const limit = Math.min(Math.max(Number(args.limit) || 10, 1), 50);
|
|
291
|
+
const searchPath = typeof args.path === "string" ? args.path : undefined;
|
|
292
|
+
if (!(yield ensureDaemonRunning())) {
|
|
293
|
+
return err("Search daemon failed to start. Run 'osgrep serve -b' manually.");
|
|
294
|
+
}
|
|
295
|
+
const server = (0, server_registry_1.getServerForProject)(projectRoot);
|
|
296
|
+
if (!server || !(0, server_registry_1.isProcessRunning)(server.pid)) {
|
|
297
|
+
return err("Search daemon not running. Run 'osgrep serve -b' manually.");
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
const response = yield fetch(`http://localhost:${server.port}/search`, {
|
|
301
|
+
method: "POST",
|
|
302
|
+
headers: { "Content-Type": "application/json" },
|
|
303
|
+
body: JSON.stringify({ query, limit, path: searchPath }),
|
|
304
|
+
signal: AbortSignal.timeout(30000),
|
|
305
|
+
});
|
|
306
|
+
if (!response.ok) {
|
|
307
|
+
const body = yield response.text();
|
|
308
|
+
return err(`Search failed (${response.status}): ${body}`);
|
|
309
|
+
}
|
|
310
|
+
const { results } = (yield response.json());
|
|
311
|
+
if (!results || results.length === 0) {
|
|
312
|
+
return ok("No matches found.");
|
|
313
|
+
}
|
|
314
|
+
const minScore = typeof args.min_score === "number" ? args.min_score : 0;
|
|
315
|
+
const maxPerFile = typeof args.max_per_file === "number" ? args.max_per_file : 0;
|
|
316
|
+
let compact = results.map((r) => {
|
|
317
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
318
|
+
return ({
|
|
319
|
+
path: (_c = (_b = (_a = r.metadata) === null || _a === void 0 ? void 0 : _a.path) !== null && _b !== void 0 ? _b : r.path) !== null && _c !== void 0 ? _c : "",
|
|
320
|
+
startLine: (_e = (_d = r.generated_metadata) === null || _d === void 0 ? void 0 : _d.start_line) !== null && _e !== void 0 ? _e : 0,
|
|
321
|
+
endLine: (_g = (_f = r.generated_metadata) === null || _f === void 0 ? void 0 : _f.end_line) !== null && _g !== void 0 ? _g : 0,
|
|
322
|
+
score: typeof r.score === "number" ? +r.score.toFixed(3) : 0,
|
|
323
|
+
role: (_h = r.role) !== null && _h !== void 0 ? _h : "IMPLEMENTATION",
|
|
324
|
+
confidence: (_j = r.confidence) !== null && _j !== void 0 ? _j : "Unknown",
|
|
325
|
+
definedSymbols: toStringArray(r.defined_symbols).slice(0, 5),
|
|
326
|
+
snippet: typeof r.text === "string" ? r.text : "",
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
if (minScore > 0) {
|
|
330
|
+
compact = compact.filter((r) => r.score >= minScore);
|
|
331
|
+
}
|
|
332
|
+
if (maxPerFile > 0) {
|
|
333
|
+
const counts = new Map();
|
|
334
|
+
compact = compact.filter((r) => {
|
|
335
|
+
const count = counts.get(r.path) || 0;
|
|
336
|
+
if (count >= maxPerFile)
|
|
337
|
+
return false;
|
|
338
|
+
counts.set(r.path, count + 1);
|
|
339
|
+
return true;
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
return ok(JSON.stringify(compact, null, 2));
|
|
343
|
+
}
|
|
344
|
+
catch (e) {
|
|
345
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
346
|
+
return err(`Search request failed: ${msg}`);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
function handleCodeSkeleton(args) {
|
|
351
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
352
|
+
const target = String(args.target || "");
|
|
353
|
+
if (!target)
|
|
354
|
+
return err("Missing required parameter: target");
|
|
355
|
+
const absPath = path.resolve(projectRoot, target);
|
|
356
|
+
const relPath = path.relative(projectRoot, absPath);
|
|
357
|
+
// Security: ensure path is within project
|
|
358
|
+
if (relPath.startsWith("..") || path.isAbsolute(relPath)) {
|
|
359
|
+
return err("Path must be within the project root.");
|
|
360
|
+
}
|
|
361
|
+
if (!fs.existsSync(absPath)) {
|
|
362
|
+
return err(`File not found: ${target}`);
|
|
363
|
+
}
|
|
364
|
+
// Try cached skeleton first
|
|
365
|
+
try {
|
|
366
|
+
const db = yield getVectorDb();
|
|
367
|
+
const cached = yield (0, retriever_1.getStoredSkeleton)(db, relPath);
|
|
368
|
+
if (cached) {
|
|
369
|
+
const tokens = Math.ceil(cached.length / 4);
|
|
370
|
+
return ok(`// ${relPath} (~${tokens} tokens)\n\n${cached}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
catch (_a) {
|
|
374
|
+
// Index may not exist yet — fall through to live generation
|
|
375
|
+
}
|
|
376
|
+
// Generate skeleton from file
|
|
377
|
+
try {
|
|
378
|
+
const content = fs.readFileSync(absPath, "utf-8");
|
|
379
|
+
const skel = yield getSkeletonizer();
|
|
380
|
+
const result = yield skel.skeletonizeFile(relPath, content);
|
|
381
|
+
if (!result.success && result.error) {
|
|
382
|
+
return err(`Skeleton generation failed: ${result.error}`);
|
|
383
|
+
}
|
|
384
|
+
return ok(`// ${relPath} (~${result.tokenEstimate} tokens)\n\n${result.skeleton}`);
|
|
385
|
+
}
|
|
386
|
+
catch (e) {
|
|
387
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
388
|
+
return err(`Skeleton failed: ${msg}`);
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
function handleTraceCalls(args) {
|
|
393
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
394
|
+
const symbol = String(args.symbol || "");
|
|
395
|
+
if (!symbol)
|
|
396
|
+
return err("Missing required parameter: symbol");
|
|
397
|
+
try {
|
|
398
|
+
const db = yield getVectorDb();
|
|
399
|
+
const builder = new graph_builder_1.GraphBuilder(db);
|
|
400
|
+
const graph = yield builder.buildGraph(symbol);
|
|
401
|
+
if (!graph.center) {
|
|
402
|
+
return ok(`Symbol '${symbol}' not found in the index.`);
|
|
403
|
+
}
|
|
404
|
+
const lines = [];
|
|
405
|
+
// Callers
|
|
406
|
+
if (graph.callers.length > 0) {
|
|
407
|
+
lines.push("Callers (who calls this?):");
|
|
408
|
+
for (const caller of graph.callers) {
|
|
409
|
+
lines.push(` <- ${caller.symbol} (${caller.file}:${caller.line})`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
lines.push("No known callers.");
|
|
414
|
+
}
|
|
415
|
+
lines.push("");
|
|
416
|
+
// Center
|
|
417
|
+
lines.push(`${graph.center.symbol}`);
|
|
418
|
+
lines.push(` Defined in ${graph.center.file}:${graph.center.line}`);
|
|
419
|
+
lines.push(` Role: ${graph.center.role}`);
|
|
420
|
+
lines.push("");
|
|
421
|
+
// Callees
|
|
422
|
+
if (graph.callees.length > 0) {
|
|
423
|
+
lines.push("Callees (what does this call?):");
|
|
424
|
+
for (const callee of graph.callees) {
|
|
425
|
+
lines.push(` -> ${callee}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
lines.push("No known callees.");
|
|
430
|
+
}
|
|
431
|
+
return ok(lines.join("\n"));
|
|
432
|
+
}
|
|
433
|
+
catch (e) {
|
|
434
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
435
|
+
return err(`Trace failed: ${msg}`);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
function handleListSymbols(args) {
|
|
440
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
441
|
+
const pattern = typeof args.pattern === "string" ? args.pattern : undefined;
|
|
442
|
+
const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 100);
|
|
443
|
+
const pathPrefix = typeof args.path === "string" ? args.path : undefined;
|
|
444
|
+
try {
|
|
445
|
+
const db = yield getVectorDb();
|
|
446
|
+
const table = yield db.ensureTable();
|
|
447
|
+
let query = table
|
|
448
|
+
.query()
|
|
449
|
+
.select(["defined_symbols", "path", "start_line"])
|
|
450
|
+
.where("array_length(defined_symbols) > 0")
|
|
451
|
+
.limit(pattern ? 10000 : Math.max(limit * 50, 2000));
|
|
452
|
+
if (pathPrefix) {
|
|
453
|
+
query = query.where(`path LIKE '${(0, filter_builder_1.escapeSqlString)((0, filter_builder_1.normalizePath)(pathPrefix))}%'`);
|
|
454
|
+
}
|
|
455
|
+
const rows = yield query.toArray();
|
|
456
|
+
const map = new Map();
|
|
457
|
+
for (const row of rows) {
|
|
458
|
+
const defs = toStringArray(row.defined_symbols);
|
|
459
|
+
const rowPath = String(row.path || "");
|
|
460
|
+
const line = Number(row.start_line || 0);
|
|
461
|
+
for (const sym of defs) {
|
|
462
|
+
if (pattern && !sym.toLowerCase().includes(pattern.toLowerCase())) {
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
const existing = map.get(sym);
|
|
466
|
+
if (existing) {
|
|
467
|
+
existing.count += 1;
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
map.set(sym, {
|
|
471
|
+
symbol: sym,
|
|
472
|
+
count: 1,
|
|
473
|
+
path: rowPath,
|
|
474
|
+
line: Math.max(1, line + 1),
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
const entries = Array.from(map.values())
|
|
480
|
+
.sort((a, b) => {
|
|
481
|
+
if (b.count !== a.count)
|
|
482
|
+
return b.count - a.count;
|
|
483
|
+
return a.symbol.localeCompare(b.symbol);
|
|
484
|
+
})
|
|
485
|
+
.slice(0, limit);
|
|
486
|
+
if (entries.length === 0) {
|
|
487
|
+
return ok("No symbols found. Run 'osgrep index' to build the index.");
|
|
488
|
+
}
|
|
489
|
+
return ok(JSON.stringify(entries, null, 2));
|
|
490
|
+
}
|
|
491
|
+
catch (e) {
|
|
492
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
493
|
+
return err(`Symbol listing failed: ${msg}`);
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
function handleIndexStatus() {
|
|
498
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
499
|
+
var _a, _b, _c, _d, _e;
|
|
500
|
+
yield ensureDaemonRunning();
|
|
501
|
+
const server = (0, server_registry_1.getServerForProject)(projectRoot);
|
|
502
|
+
if (!server || !(0, server_registry_1.isProcessRunning)(server.pid)) {
|
|
503
|
+
// Fall back to config file
|
|
504
|
+
const configPath = path.join(projectRoot, ".osgrep", "config.json");
|
|
505
|
+
try {
|
|
506
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
507
|
+
return ok(JSON.stringify({
|
|
508
|
+
daemon: "stopped",
|
|
509
|
+
embedMode: (_a = config.embedMode) !== null && _a !== void 0 ? _a : "unknown",
|
|
510
|
+
model: (_c = (_b = config.embedModel) !== null && _b !== void 0 ? _b : config.mlxModel) !== null && _c !== void 0 ? _c : null,
|
|
511
|
+
vectorDim: (_d = config.vectorDim) !== null && _d !== void 0 ? _d : null,
|
|
512
|
+
indexedAt: (_e = config.indexedAt) !== null && _e !== void 0 ? _e : null,
|
|
513
|
+
}, null, 2));
|
|
514
|
+
}
|
|
515
|
+
catch (_f) {
|
|
516
|
+
return ok(JSON.stringify({ daemon: "stopped", indexed: false }));
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
try {
|
|
520
|
+
const response = yield fetch(`http://localhost:${server.port}/stats`, {
|
|
521
|
+
signal: AbortSignal.timeout(5000),
|
|
522
|
+
});
|
|
523
|
+
if (!response.ok) {
|
|
524
|
+
return err(`Stats request failed (${response.status})`);
|
|
525
|
+
}
|
|
526
|
+
const stats = yield response.json();
|
|
527
|
+
return ok(JSON.stringify(Object.assign({ daemon: "running", pid: server.pid, port: server.port }, stats), null, 2));
|
|
528
|
+
}
|
|
529
|
+
catch (e) {
|
|
530
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
531
|
+
return err(`Failed to get status: ${msg}`);
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
// --- MCP server setup ---
|
|
536
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
537
|
+
const server = new index_js_1.Server({
|
|
538
|
+
name: "osgrep",
|
|
539
|
+
version: JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json"), {
|
|
540
|
+
encoding: "utf-8",
|
|
541
|
+
})).version,
|
|
542
|
+
}, {
|
|
543
|
+
capabilities: {
|
|
544
|
+
tools: {},
|
|
545
|
+
},
|
|
546
|
+
});
|
|
547
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
548
|
+
return { tools: TOOLS };
|
|
549
|
+
}));
|
|
550
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, (request) => __awaiter(void 0, void 0, void 0, function* () {
|
|
551
|
+
const { name, arguments: args } = request.params;
|
|
552
|
+
const toolArgs = (args !== null && args !== void 0 ? args : {});
|
|
553
|
+
switch (name) {
|
|
554
|
+
case "semantic_search":
|
|
555
|
+
return handleSemanticSearch(toolArgs);
|
|
556
|
+
case "code_skeleton":
|
|
557
|
+
return handleCodeSkeleton(toolArgs);
|
|
558
|
+
case "trace_calls":
|
|
559
|
+
return handleTraceCalls(toolArgs);
|
|
560
|
+
case "list_symbols":
|
|
561
|
+
return handleListSymbols(toolArgs);
|
|
562
|
+
case "index_status":
|
|
563
|
+
return handleIndexStatus();
|
|
564
|
+
default:
|
|
565
|
+
return err(`Unknown tool: ${name}`);
|
|
566
|
+
}
|
|
567
|
+
}));
|
|
568
|
+
yield server.connect(transport);
|
|
569
|
+
// Ensure the serve daemon is running (handles indexing, GPU, live reindex).
|
|
570
|
+
// The MCP server owns daemon lifecycle — the SessionStart hook is read-only.
|
|
571
|
+
_daemonReady = ensureDaemon(projectRoot);
|
|
572
|
+
}));
|