@rpcajr/smart-graph-indexer 1.0.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/.agents/rules/antigravity-rtk-rules.md +32 -0
- package/README.md +116 -0
- package/dist/graph.d.ts +32 -0
- package/dist/graph.js +86 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +227 -0
- package/dist/mcp_server.d.ts +48 -0
- package/dist/mcp_server.js +433 -0
- package/dist/parser.d.ts +42 -0
- package/dist/parser.js +529 -0
- package/dist/stats.d.ts +43 -0
- package/dist/stats.js +146 -0
- package/dist/ui.d.ts +2 -0
- package/dist/ui.js +1003 -0
- package/dist/vector.d.ts +66 -0
- package/dist/vector.js +144 -0
- package/dist/wasm/tree-sitter-c.wasm +0 -0
- package/dist/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/dist/wasm/tree-sitter-cpp.wasm +0 -0
- package/dist/wasm/tree-sitter-go.wasm +0 -0
- package/dist/wasm/tree-sitter-java.wasm +0 -0
- package/dist/wasm/tree-sitter-python.wasm +0 -0
- package/dist/wasm/tree-sitter-ruby.wasm +0 -0
- package/dist/wasm/tree-sitter-rust.wasm +0 -0
- package/dist/wasm/tree-sitter-typescript.wasm +0 -0
- package/dist/wasm/tree-sitter.wasm +0 -0
- package/dist/watcher.d.ts +36 -0
- package/dist/watcher.js +166 -0
- package/mcp-config-example.json +12 -0
- package/mcp_smart_indexer_implementation_spec.md +366 -0
- package/package.json +35 -0
- package/src/graph.ts +93 -0
- package/src/index.ts +216 -0
- package/src/mcp_server.ts +454 -0
- package/src/parser.ts +484 -0
- package/src/stats.ts +156 -0
- package/src/ui.ts +956 -0
- package/src/vector.ts +166 -0
- package/src/watcher.ts +144 -0
- package/test_project/App.java +16 -0
- package/test_project/BillingService.cs +31 -0
- package/test_project/auth.ts +18 -0
- package/test_project/config.ts +11 -0
- package/test_project/database.ts +21 -0
- package/test_project/index.ts +13 -0
- package/test_project/main.go +11 -0
- package/test_project/main.py +21 -0
- package/test_project/processor.py +12 -0
- package/test_project/utils.py +13 -0
- package/tsconfig.json +16 -0
- package/wasm/tree-sitter-c.wasm +0 -0
- package/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/wasm/tree-sitter-cpp.wasm +0 -0
- package/wasm/tree-sitter-go.wasm +0 -0
- package/wasm/tree-sitter-java.wasm +0 -0
- package/wasm/tree-sitter-python.wasm +0 -0
- package/wasm/tree-sitter-ruby.wasm +0 -0
- package/wasm/tree-sitter-rust.wasm +0 -0
- package/wasm/tree-sitter-typescript.wasm +0 -0
- package/wasm/tree-sitter.wasm +0 -0
|
@@ -0,0 +1,433 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.IndexerEngine = void 0;
|
|
37
|
+
exports.createMCPServer = createMCPServer;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
41
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
42
|
+
const parser_js_1 = require("./parser.js");
|
|
43
|
+
const graph_js_1 = require("./graph.js");
|
|
44
|
+
const vector_js_1 = require("./vector.js");
|
|
45
|
+
const watcher_js_1 = require("./watcher.js");
|
|
46
|
+
const stats_js_1 = require("./stats.js");
|
|
47
|
+
class IndexerEngine {
|
|
48
|
+
projectPath;
|
|
49
|
+
parser;
|
|
50
|
+
graph;
|
|
51
|
+
vectorStore;
|
|
52
|
+
watcher;
|
|
53
|
+
fileSkeletons = new Map();
|
|
54
|
+
isLoaded = false;
|
|
55
|
+
stats;
|
|
56
|
+
constructor(projectPath, onUpdate) {
|
|
57
|
+
this.projectPath = path.resolve(projectPath);
|
|
58
|
+
this.stats = new stats_js_1.StatsManager(this.projectPath);
|
|
59
|
+
this.parser = new parser_js_1.ASTParser();
|
|
60
|
+
this.graph = new graph_js_1.DependencyGraph();
|
|
61
|
+
this.vectorStore = new vector_js_1.VectorStore();
|
|
62
|
+
this.watcher = new watcher_js_1.GitAwareWatcher({
|
|
63
|
+
projectPath: this.projectPath,
|
|
64
|
+
onChange: async (changedFiles) => {
|
|
65
|
+
console.error(`[Watcher] Files modified, re-indexing: ${changedFiles.join(', ')}`);
|
|
66
|
+
for (const file of changedFiles) {
|
|
67
|
+
await this.indexFile(file);
|
|
68
|
+
}
|
|
69
|
+
if (onUpdate) {
|
|
70
|
+
onUpdate();
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
updateProjectPath(newPath, onUpdate) {
|
|
76
|
+
this.projectPath = path.resolve(newPath);
|
|
77
|
+
this.stats = new stats_js_1.StatsManager(this.projectPath);
|
|
78
|
+
this.watcher = new watcher_js_1.GitAwareWatcher({
|
|
79
|
+
projectPath: this.projectPath,
|
|
80
|
+
onChange: async (changedFiles) => {
|
|
81
|
+
console.error(`[Watcher] Files modified, re-indexing: ${changedFiles.join(', ')}`);
|
|
82
|
+
for (const file of changedFiles) {
|
|
83
|
+
await this.indexFile(file);
|
|
84
|
+
}
|
|
85
|
+
if (onUpdate) {
|
|
86
|
+
onUpdate();
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
startWatcher() {
|
|
92
|
+
this.watcher.start();
|
|
93
|
+
}
|
|
94
|
+
async initialize() {
|
|
95
|
+
const wasmDir = path.join(__dirname, 'wasm');
|
|
96
|
+
await this.parser.init(wasmDir);
|
|
97
|
+
await this.vectorStore.init();
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Scans and indexes the entire directory recursively.
|
|
101
|
+
*/
|
|
102
|
+
async startIndexing() {
|
|
103
|
+
await this.initialize();
|
|
104
|
+
// Check if the workspace is initialized for SmartGraphIndexer (.graph-indexer folder exists)
|
|
105
|
+
const mcpDir = path.join(this.projectPath, '.graph-indexer');
|
|
106
|
+
if (!fs.existsSync(mcpDir)) {
|
|
107
|
+
console.error(`[Indexer] .graph-indexer directory not found at: ${this.projectPath}. SmartGraphIndexerMCP is dormant for this workspace.`);
|
|
108
|
+
this.isLoaded = true;
|
|
109
|
+
return; // Do not scan or watch
|
|
110
|
+
}
|
|
111
|
+
const deltas = await this.watcher.getGitDeltas();
|
|
112
|
+
console.error(`[Indexer] Git status deltas detected: ${deltas.length} files.`);
|
|
113
|
+
// Recursive scan
|
|
114
|
+
await this.scanDir(this.projectPath);
|
|
115
|
+
this.isLoaded = true;
|
|
116
|
+
// Start watching filesystem
|
|
117
|
+
this.watcher.start();
|
|
118
|
+
console.error('[Indexer] Full initial indexing completed. File watcher active.');
|
|
119
|
+
}
|
|
120
|
+
async scanDir(dir) {
|
|
121
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
122
|
+
for (const entry of entries) {
|
|
123
|
+
const fullPath = path.join(dir, entry.name);
|
|
124
|
+
const relativePath = path.relative(this.projectPath, fullPath).replace(/\\/g, '/');
|
|
125
|
+
if (this.watcher.isIgnored(relativePath)) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (entry.isDirectory()) {
|
|
129
|
+
await this.scanDir(fullPath);
|
|
130
|
+
}
|
|
131
|
+
else if (entry.isFile()) {
|
|
132
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
133
|
+
const supported = [
|
|
134
|
+
'.cs', '.ts', '.tsx', '.js', '.jsx', '.py',
|
|
135
|
+
'.java', '.go', '.rs', '.rb',
|
|
136
|
+
'.cpp', '.hpp', '.cc', '.cxx', '.h', '.c'
|
|
137
|
+
];
|
|
138
|
+
if (supported.includes(ext)) {
|
|
139
|
+
await this.indexFile(relativePath);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Indexes a single file.
|
|
146
|
+
*/
|
|
147
|
+
async indexFile(relativeFilePath) {
|
|
148
|
+
const normalizedPath = relativeFilePath.replace(/\\/g, '/');
|
|
149
|
+
const fullPath = path.resolve(this.projectPath, normalizedPath);
|
|
150
|
+
if (!fs.existsSync(fullPath)) {
|
|
151
|
+
this.fileSkeletons.delete(normalizedPath);
|
|
152
|
+
this.graph.removeFile(normalizedPath);
|
|
153
|
+
this.vectorStore.removeFileSymbols(normalizedPath);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
158
|
+
const hash = this.parser.getHash(content);
|
|
159
|
+
const cached = this.fileSkeletons.get(normalizedPath);
|
|
160
|
+
if (cached && cached.file_hash === hash) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const skeleton = this.parser.parseFile(normalizedPath, content);
|
|
164
|
+
this.fileSkeletons.set(normalizedPath, skeleton);
|
|
165
|
+
this.graph.updateFileDependencies(normalizedPath, skeleton.dependencies);
|
|
166
|
+
this.vectorStore.removeFileSymbols(normalizedPath);
|
|
167
|
+
for (const symbol of skeleton.symbols) {
|
|
168
|
+
await this.vectorStore.addSymbol(normalizedPath, symbol);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
console.error(`Error indexing file ${normalizedPath}:`, err);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Gracefully updates a file on demand (for JIT read operations).
|
|
177
|
+
*/
|
|
178
|
+
async ensureFileUpToDate(relativeFilePath) {
|
|
179
|
+
await this.indexFile(relativeFilePath);
|
|
180
|
+
return this.fileSkeletons.get(relativeFilePath);
|
|
181
|
+
}
|
|
182
|
+
getFileSkeleton(relativeFilePath) {
|
|
183
|
+
return this.fileSkeletons.get(relativeFilePath);
|
|
184
|
+
}
|
|
185
|
+
getGraph() {
|
|
186
|
+
return this.graph;
|
|
187
|
+
}
|
|
188
|
+
getVectorStore() {
|
|
189
|
+
return this.vectorStore;
|
|
190
|
+
}
|
|
191
|
+
getStatsManager() {
|
|
192
|
+
return this.stats;
|
|
193
|
+
}
|
|
194
|
+
async shutdown() {
|
|
195
|
+
await this.watcher.stop();
|
|
196
|
+
}
|
|
197
|
+
getReport() {
|
|
198
|
+
return {
|
|
199
|
+
totalFiles: this.fileSkeletons.size,
|
|
200
|
+
files: Array.from(this.fileSkeletons.keys()),
|
|
201
|
+
symbolsCount: this.vectorStore.getAllSymbols().length,
|
|
202
|
+
dependencyGraph: this.graph.serialize(),
|
|
203
|
+
tokenStats: this.stats.getStats(),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
exports.IndexerEngine = IndexerEngine;
|
|
208
|
+
function createMCPServer(engine) {
|
|
209
|
+
const server = new index_js_1.Server({
|
|
210
|
+
name: 'SmartGraphIndexerMCP',
|
|
211
|
+
version: '1.0.0',
|
|
212
|
+
}, {
|
|
213
|
+
capabilities: {
|
|
214
|
+
tools: {},
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
// 1. List available tools
|
|
218
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
219
|
+
return {
|
|
220
|
+
tools: [
|
|
221
|
+
{
|
|
222
|
+
name: 'search_symbols',
|
|
223
|
+
description: 'Performs hybrid search (semantic via vectors + lexical via text) over the indexed signatures database.',
|
|
224
|
+
inputSchema: {
|
|
225
|
+
type: 'object',
|
|
226
|
+
properties: {
|
|
227
|
+
query: {
|
|
228
|
+
type: 'string',
|
|
229
|
+
description: 'Search term or functional description of the logic you want to find.',
|
|
230
|
+
},
|
|
231
|
+
limit: {
|
|
232
|
+
type: 'integer',
|
|
233
|
+
description: 'Maximum number of matches to return.',
|
|
234
|
+
default: 5,
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
required: ['query'],
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
name: 'get_file_skeleton',
|
|
242
|
+
description: 'Returns the complete structure (skeleton) of a specific file without inner implementations.',
|
|
243
|
+
inputSchema: {
|
|
244
|
+
type: 'object',
|
|
245
|
+
properties: {
|
|
246
|
+
file_path: {
|
|
247
|
+
type: 'string',
|
|
248
|
+
description: 'Relative path of the file in the project.',
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
required: ['file_path'],
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
name: 'get_dependencies',
|
|
256
|
+
description: 'Queries the in-memory graph and returns imports/dependants of a specific file.',
|
|
257
|
+
inputSchema: {
|
|
258
|
+
type: 'object',
|
|
259
|
+
properties: {
|
|
260
|
+
file_path: {
|
|
261
|
+
type: 'string',
|
|
262
|
+
description: 'Relative path of the source file.',
|
|
263
|
+
},
|
|
264
|
+
direction: {
|
|
265
|
+
type: 'string',
|
|
266
|
+
enum: ['incoming', 'outgoing', 'both'],
|
|
267
|
+
description: 'Direction of dependencies to query.',
|
|
268
|
+
default: 'both',
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
required: ['file_path'],
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: 'read_symbol_body',
|
|
276
|
+
description: 'JIT Extraction. Returns the actual, complete source code of a specific method or class based on coordinates.',
|
|
277
|
+
inputSchema: {
|
|
278
|
+
type: 'object',
|
|
279
|
+
properties: {
|
|
280
|
+
file_path: {
|
|
281
|
+
type: 'string',
|
|
282
|
+
description: 'Relative path of the file.',
|
|
283
|
+
},
|
|
284
|
+
symbol_name: {
|
|
285
|
+
type: 'string',
|
|
286
|
+
description: 'Exact name of the class or method to read.',
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
required: ['file_path', 'symbol_name'],
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
name: 'get_token_savings',
|
|
294
|
+
description: 'Returns token savings statistics accumulated in the project by using this MCP.',
|
|
295
|
+
inputSchema: {
|
|
296
|
+
type: 'object',
|
|
297
|
+
properties: {},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
};
|
|
302
|
+
});
|
|
303
|
+
// 2. Handle tool calls
|
|
304
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
305
|
+
const { name, arguments: args } = request.params;
|
|
306
|
+
const startTime = Date.now();
|
|
307
|
+
try {
|
|
308
|
+
if (name === 'search_symbols') {
|
|
309
|
+
const query = String(args?.query);
|
|
310
|
+
const limit = Number(args?.limit ?? 5);
|
|
311
|
+
const results = await engine.getVectorStore().search(query, limit);
|
|
312
|
+
const actualPayload = JSON.stringify(results, null, 2);
|
|
313
|
+
let alternativeText = '';
|
|
314
|
+
for (const res of results) {
|
|
315
|
+
const fullPath = path.resolve(engine.projectPath, res.symbol.filePath);
|
|
316
|
+
if (fs.existsSync(fullPath)) {
|
|
317
|
+
alternativeText += fs.readFileSync(fullPath, 'utf-8');
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const execTimeMs = Date.now() - startTime;
|
|
321
|
+
engine.getStatsManager().recordCall('search_symbols', actualPayload, alternativeText, `Query: "${query}"`, execTimeMs);
|
|
322
|
+
return {
|
|
323
|
+
content: [
|
|
324
|
+
{
|
|
325
|
+
type: 'text',
|
|
326
|
+
text: actualPayload,
|
|
327
|
+
},
|
|
328
|
+
],
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
if (name === 'get_file_skeleton') {
|
|
332
|
+
const filePath = String(args?.file_path).replace(/\\/g, '/');
|
|
333
|
+
const skeleton = await engine.ensureFileUpToDate(filePath);
|
|
334
|
+
if (!skeleton) {
|
|
335
|
+
throw new Error(`File ${filePath} not found or could not be parsed.`);
|
|
336
|
+
}
|
|
337
|
+
const actualPayload = JSON.stringify(skeleton, null, 2);
|
|
338
|
+
const fullPath = path.resolve(engine.projectPath, filePath);
|
|
339
|
+
const alternativeText = fs.existsSync(fullPath) ? fs.readFileSync(fullPath, 'utf-8') : '';
|
|
340
|
+
const execTimeMs = Date.now() - startTime;
|
|
341
|
+
engine.getStatsManager().recordCall('get_file_skeleton', actualPayload, alternativeText, filePath, execTimeMs);
|
|
342
|
+
return {
|
|
343
|
+
content: [
|
|
344
|
+
{
|
|
345
|
+
type: 'text',
|
|
346
|
+
text: actualPayload,
|
|
347
|
+
},
|
|
348
|
+
],
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
if (name === 'get_dependencies') {
|
|
352
|
+
const filePath = String(args?.file_path).replace(/\\/g, '/');
|
|
353
|
+
const direction = String(args?.direction ?? 'both');
|
|
354
|
+
const graph = engine.getGraph();
|
|
355
|
+
const result = {};
|
|
356
|
+
if (direction === 'outgoing' || direction === 'both') {
|
|
357
|
+
result.outgoing = graph.getOutgoing(filePath);
|
|
358
|
+
}
|
|
359
|
+
if (direction === 'incoming' || direction === 'both') {
|
|
360
|
+
result.incoming = graph.getIncoming(filePath);
|
|
361
|
+
}
|
|
362
|
+
const actualPayload = JSON.stringify(result, null, 2);
|
|
363
|
+
const fullPath = path.resolve(engine.projectPath, filePath);
|
|
364
|
+
const alternativeText = fs.existsSync(fullPath) ? fs.readFileSync(fullPath, 'utf-8') : '';
|
|
365
|
+
const execTimeMs = Date.now() - startTime;
|
|
366
|
+
engine.getStatsManager().recordCall('get_dependencies', actualPayload, alternativeText, filePath, execTimeMs);
|
|
367
|
+
return {
|
|
368
|
+
content: [
|
|
369
|
+
{
|
|
370
|
+
type: 'text',
|
|
371
|
+
text: actualPayload,
|
|
372
|
+
},
|
|
373
|
+
],
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
if (name === 'read_symbol_body') {
|
|
377
|
+
const filePath = String(args?.file_path).replace(/\\/g, '/');
|
|
378
|
+
const symbolName = String(args?.symbol_name);
|
|
379
|
+
const skeleton = await engine.ensureFileUpToDate(filePath);
|
|
380
|
+
if (!skeleton) {
|
|
381
|
+
throw new Error(`File ${filePath} not found or could not be parsed.`);
|
|
382
|
+
}
|
|
383
|
+
const symbol = skeleton.symbols.find(s => s.name === symbolName);
|
|
384
|
+
if (!symbol) {
|
|
385
|
+
throw new Error(`Symbol ${symbolName} not found in ${filePath}.`);
|
|
386
|
+
}
|
|
387
|
+
const fullPath = path.resolve(engine.projectPath, filePath);
|
|
388
|
+
if (!fs.existsSync(fullPath)) {
|
|
389
|
+
throw new Error(`File ${filePath} does not exist on disk.`);
|
|
390
|
+
}
|
|
391
|
+
const fileLines = fs.readFileSync(fullPath, 'utf-8').split('\n');
|
|
392
|
+
const start = Math.max(0, symbol.start_line - 1);
|
|
393
|
+
const end = Math.min(fileLines.length, symbol.end_line);
|
|
394
|
+
const codeSlice = fileLines.slice(start, end).join('\n');
|
|
395
|
+
const fullContent = fileLines.join('\n');
|
|
396
|
+
const execTimeMs = Date.now() - startTime;
|
|
397
|
+
engine.getStatsManager().recordCall('read_symbol_body', codeSlice, fullContent, `${filePath} -> ${symbolName}`, execTimeMs);
|
|
398
|
+
return {
|
|
399
|
+
content: [
|
|
400
|
+
{
|
|
401
|
+
type: 'text',
|
|
402
|
+
text: codeSlice,
|
|
403
|
+
},
|
|
404
|
+
],
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
if (name === 'get_token_savings') {
|
|
408
|
+
const stats = engine.getStatsManager().getStats();
|
|
409
|
+
return {
|
|
410
|
+
content: [
|
|
411
|
+
{
|
|
412
|
+
type: 'text',
|
|
413
|
+
text: JSON.stringify(stats, null, 2),
|
|
414
|
+
},
|
|
415
|
+
],
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
419
|
+
}
|
|
420
|
+
catch (err) {
|
|
421
|
+
return {
|
|
422
|
+
isError: true,
|
|
423
|
+
content: [
|
|
424
|
+
{
|
|
425
|
+
type: 'text',
|
|
426
|
+
text: err?.message || String(err),
|
|
427
|
+
},
|
|
428
|
+
],
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
return server;
|
|
433
|
+
}
|
package/dist/parser.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface SymbolSkeleton {
|
|
2
|
+
name: string;
|
|
3
|
+
kind: 'Class' | 'Struct' | 'Method' | 'Property' | 'Interface' | 'Function' | 'Enum' | 'Trait';
|
|
4
|
+
signature: string;
|
|
5
|
+
docstring?: string;
|
|
6
|
+
start_line: number;
|
|
7
|
+
end_line: number;
|
|
8
|
+
}
|
|
9
|
+
export interface FileSkeleton {
|
|
10
|
+
file_path: string;
|
|
11
|
+
file_hash: string;
|
|
12
|
+
symbols: SymbolSkeleton[];
|
|
13
|
+
dependencies: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare class ASTParser {
|
|
16
|
+
private isInitialized;
|
|
17
|
+
private languages;
|
|
18
|
+
constructor();
|
|
19
|
+
/**
|
|
20
|
+
* Initializes the web-tree-sitter parser and loads language WASM files.
|
|
21
|
+
*/
|
|
22
|
+
init(wasmDir: string): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Calculates MD5 hash of file content.
|
|
25
|
+
*/
|
|
26
|
+
getHash(content: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Parsers a file and extracts its skeleton.
|
|
29
|
+
*/
|
|
30
|
+
parseFile(filePath: string, content: string): FileSkeleton;
|
|
31
|
+
private extractDependencies;
|
|
32
|
+
private getPrecedingDocstring;
|
|
33
|
+
private parseCSharp;
|
|
34
|
+
private parseTypeScript;
|
|
35
|
+
private parsePython;
|
|
36
|
+
private getPythonDocstring;
|
|
37
|
+
private parseJava;
|
|
38
|
+
private parseGo;
|
|
39
|
+
private parseRust;
|
|
40
|
+
private parseRuby;
|
|
41
|
+
private parseCpp;
|
|
42
|
+
}
|