magector 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/LICENSE +21 -0
- package/README.md +627 -0
- package/config/mcp-config.json +13 -0
- package/package.json +53 -0
- package/src/binary.js +66 -0
- package/src/cli.js +203 -0
- package/src/init.js +293 -0
- package/src/magento-patterns.js +563 -0
- package/src/mcp-server.js +915 -0
- package/src/model.js +127 -0
- package/src/templates/claude-md.js +47 -0
- package/src/templates/cursorrules.js +45 -0
- package/src/validation/accuracy-calculator.js +397 -0
- package/src/validation/benchmark.js +355 -0
- package/src/validation/test-data-generator.js +672 -0
- package/src/validation/test-queries.js +326 -0
- package/src/validation/validator.js +302 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Comprehensive benchmark suite for Magector
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { MagentoIndexer } from '../indexer.js';
|
|
7
|
+
import { generateCompleteMockModule, MOCK_MODULES } from './test-data-generator.js';
|
|
8
|
+
import { TEST_QUERIES } from './test-queries.js';
|
|
9
|
+
import { writeFile, mkdir, rm } from 'fs/promises';
|
|
10
|
+
import { existsSync } from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
|
|
13
|
+
const BENCHMARK_DIR = '/private/tmp/magector-benchmark';
|
|
14
|
+
|
|
15
|
+
class BenchmarkSuite {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.results = {
|
|
18
|
+
indexing: {},
|
|
19
|
+
search: {},
|
|
20
|
+
memory: {},
|
|
21
|
+
gnn: {}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async setup(moduleCount = 5) {
|
|
26
|
+
console.log(`\nSetting up benchmark with ${moduleCount} modules...`);
|
|
27
|
+
|
|
28
|
+
if (existsSync(BENCHMARK_DIR)) {
|
|
29
|
+
await rm(BENCHMARK_DIR, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
await mkdir(`${BENCHMARK_DIR}/app/code`, { recursive: true });
|
|
32
|
+
|
|
33
|
+
let totalFiles = 0;
|
|
34
|
+
const modules = MOCK_MODULES.slice(0, moduleCount);
|
|
35
|
+
|
|
36
|
+
for (const moduleName of modules) {
|
|
37
|
+
const files = generateCompleteMockModule(moduleName);
|
|
38
|
+
totalFiles += files.length;
|
|
39
|
+
|
|
40
|
+
for (const file of files) {
|
|
41
|
+
const filePath = path.join(BENCHMARK_DIR, file.path);
|
|
42
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
43
|
+
await writeFile(filePath, file.content);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(` Generated ${totalFiles} files in ${moduleCount} modules`);
|
|
48
|
+
return { modules: moduleCount, files: totalFiles };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async benchmarkIndexing() {
|
|
52
|
+
console.log('\nš Benchmarking Indexing Performance...');
|
|
53
|
+
|
|
54
|
+
const configurations = [
|
|
55
|
+
{ name: 'Basic (no GNN)', enableGNN: false },
|
|
56
|
+
{ name: 'Full GNN', enableGNN: true }
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
for (const config of configurations) {
|
|
60
|
+
const dbPath = `${BENCHMARK_DIR}/bench-${config.name.replace(/\s/g, '-')}.db`;
|
|
61
|
+
const graphPath = `${BENCHMARK_DIR}/bench-${config.name.replace(/\s/g, '-')}-graph.json`;
|
|
62
|
+
|
|
63
|
+
const indexer = new MagentoIndexer({
|
|
64
|
+
dbPath,
|
|
65
|
+
graphPath,
|
|
66
|
+
magentoRoot: BENCHMARK_DIR,
|
|
67
|
+
enableGNN: config.enableGNN
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
await indexer.init();
|
|
71
|
+
|
|
72
|
+
const startTime = Date.now();
|
|
73
|
+
const startMemory = process.memoryUsage().heapUsed;
|
|
74
|
+
|
|
75
|
+
const stats = await indexer.indexDirectory();
|
|
76
|
+
|
|
77
|
+
const endTime = Date.now();
|
|
78
|
+
const endMemory = process.memoryUsage().heapUsed;
|
|
79
|
+
|
|
80
|
+
this.results.indexing[config.name] = {
|
|
81
|
+
duration: endTime - startTime,
|
|
82
|
+
filesIndexed: stats.indexed,
|
|
83
|
+
filesPerSecond: stats.indexed / ((endTime - startTime) / 1000),
|
|
84
|
+
memoryUsed: (endMemory - startMemory) / 1024 / 1024,
|
|
85
|
+
graphNodes: stats.graphNodes || 0,
|
|
86
|
+
graphEdges: stats.graphEdges || 0
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
console.log(` ${config.name}: ${endTime - startTime}ms (${stats.indexed} files)`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return this.results.indexing;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async benchmarkSearch() {
|
|
96
|
+
console.log('\nš Benchmarking Search Performance...');
|
|
97
|
+
|
|
98
|
+
const indexer = new MagentoIndexer({
|
|
99
|
+
dbPath: `${BENCHMARK_DIR}/bench-Full-GNN.db`,
|
|
100
|
+
graphPath: `${BENCHMARK_DIR}/bench-Full-GNN-graph.json`,
|
|
101
|
+
magentoRoot: BENCHMARK_DIR,
|
|
102
|
+
enableGNN: true
|
|
103
|
+
});
|
|
104
|
+
await indexer.init();
|
|
105
|
+
|
|
106
|
+
// Warm-up
|
|
107
|
+
for (let i = 0; i < 10; i++) {
|
|
108
|
+
await indexer.search('test query', { limit: 10 });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const searchTypes = [
|
|
112
|
+
{ name: 'Simple search', method: 'search', iterations: 100 },
|
|
113
|
+
{ name: 'Graph-enhanced', method: 'searchWithGraph', iterations: 100 }
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
for (const searchType of searchTypes) {
|
|
117
|
+
const latencies = [];
|
|
118
|
+
|
|
119
|
+
for (let i = 0; i < searchType.iterations; i++) {
|
|
120
|
+
const query = TEST_QUERIES[i % TEST_QUERIES.length];
|
|
121
|
+
const start = process.hrtime.bigint();
|
|
122
|
+
|
|
123
|
+
if (searchType.method === 'search') {
|
|
124
|
+
await indexer.search(query.query, { limit: 10 });
|
|
125
|
+
} else {
|
|
126
|
+
await indexer.searchWithGraph(query.query, { limit: 10 });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const end = process.hrtime.bigint();
|
|
130
|
+
latencies.push(Number(end - start) / 1e6); // Convert to ms
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
latencies.sort((a, b) => a - b);
|
|
134
|
+
|
|
135
|
+
this.results.search[searchType.name] = {
|
|
136
|
+
iterations: searchType.iterations,
|
|
137
|
+
min: latencies[0].toFixed(2),
|
|
138
|
+
max: latencies[latencies.length - 1].toFixed(2),
|
|
139
|
+
avg: (latencies.reduce((a, b) => a + b, 0) / latencies.length).toFixed(2),
|
|
140
|
+
p50: latencies[Math.floor(latencies.length * 0.5)].toFixed(2),
|
|
141
|
+
p90: latencies[Math.floor(latencies.length * 0.9)].toFixed(2),
|
|
142
|
+
p99: latencies[Math.floor(latencies.length * 0.99)].toFixed(2),
|
|
143
|
+
throughput: (1000 / (latencies.reduce((a, b) => a + b, 0) / latencies.length)).toFixed(1)
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
console.log(` ${searchType.name}: avg ${this.results.search[searchType.name].avg}ms, p99 ${this.results.search[searchType.name].p99}ms`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return this.results.search;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async benchmarkMemory() {
|
|
153
|
+
console.log('\nš Benchmarking Memory Usage...');
|
|
154
|
+
|
|
155
|
+
global.gc && global.gc();
|
|
156
|
+
const baseline = process.memoryUsage();
|
|
157
|
+
|
|
158
|
+
const indexer = new MagentoIndexer({
|
|
159
|
+
dbPath: `${BENCHMARK_DIR}/bench-memory.db`,
|
|
160
|
+
graphPath: `${BENCHMARK_DIR}/bench-memory-graph.json`,
|
|
161
|
+
magentoRoot: BENCHMARK_DIR,
|
|
162
|
+
enableGNN: true
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
await indexer.init();
|
|
166
|
+
const afterInit = process.memoryUsage();
|
|
167
|
+
|
|
168
|
+
await indexer.indexDirectory();
|
|
169
|
+
const afterIndex = process.memoryUsage();
|
|
170
|
+
|
|
171
|
+
// Run searches
|
|
172
|
+
for (let i = 0; i < 100; i++) {
|
|
173
|
+
await indexer.searchWithGraph(TEST_QUERIES[i % TEST_QUERIES.length].query, { limit: 10 });
|
|
174
|
+
}
|
|
175
|
+
const afterSearch = process.memoryUsage();
|
|
176
|
+
|
|
177
|
+
this.results.memory = {
|
|
178
|
+
baseline: {
|
|
179
|
+
heapUsed: (baseline.heapUsed / 1024 / 1024).toFixed(2),
|
|
180
|
+
heapTotal: (baseline.heapTotal / 1024 / 1024).toFixed(2),
|
|
181
|
+
rss: (baseline.rss / 1024 / 1024).toFixed(2)
|
|
182
|
+
},
|
|
183
|
+
afterInit: {
|
|
184
|
+
heapUsed: (afterInit.heapUsed / 1024 / 1024).toFixed(2),
|
|
185
|
+
heapTotal: (afterInit.heapTotal / 1024 / 1024).toFixed(2),
|
|
186
|
+
delta: ((afterInit.heapUsed - baseline.heapUsed) / 1024 / 1024).toFixed(2)
|
|
187
|
+
},
|
|
188
|
+
afterIndex: {
|
|
189
|
+
heapUsed: (afterIndex.heapUsed / 1024 / 1024).toFixed(2),
|
|
190
|
+
heapTotal: (afterIndex.heapTotal / 1024 / 1024).toFixed(2),
|
|
191
|
+
delta: ((afterIndex.heapUsed - afterInit.heapUsed) / 1024 / 1024).toFixed(2)
|
|
192
|
+
},
|
|
193
|
+
afterSearch: {
|
|
194
|
+
heapUsed: (afterSearch.heapUsed / 1024 / 1024).toFixed(2),
|
|
195
|
+
heapTotal: (afterSearch.heapTotal / 1024 / 1024).toFixed(2),
|
|
196
|
+
delta: ((afterSearch.heapUsed - afterIndex.heapUsed) / 1024 / 1024).toFixed(2)
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
console.log(` Heap after indexing: ${this.results.memory.afterIndex.heapUsed}MB`);
|
|
201
|
+
console.log(` Heap after 100 searches: ${this.results.memory.afterSearch.heapUsed}MB`);
|
|
202
|
+
|
|
203
|
+
return this.results.memory;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async benchmarkGNN() {
|
|
207
|
+
console.log('\nš Benchmarking GNN Features...');
|
|
208
|
+
|
|
209
|
+
const indexer = new MagentoIndexer({
|
|
210
|
+
dbPath: `${BENCHMARK_DIR}/bench-Full-GNN.db`,
|
|
211
|
+
graphPath: `${BENCHMARK_DIR}/bench-Full-GNN-graph.json`,
|
|
212
|
+
magentoRoot: BENCHMARK_DIR,
|
|
213
|
+
enableGNN: true
|
|
214
|
+
});
|
|
215
|
+
await indexer.init();
|
|
216
|
+
|
|
217
|
+
// Test graph loading
|
|
218
|
+
const startLoad = Date.now();
|
|
219
|
+
const graph = await indexer.loadGraph();
|
|
220
|
+
const loadTime = Date.now() - startLoad;
|
|
221
|
+
|
|
222
|
+
// Test dependency finding
|
|
223
|
+
const depLatencies = [];
|
|
224
|
+
const testClasses = ['Index', 'ItemRepository', 'ProductSaveObserver', 'ItemList'];
|
|
225
|
+
|
|
226
|
+
for (const className of testClasses) {
|
|
227
|
+
const start = Date.now();
|
|
228
|
+
await indexer.findDependencies(className);
|
|
229
|
+
depLatencies.push(Date.now() - start);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Test graph traversal
|
|
233
|
+
const traversalStart = Date.now();
|
|
234
|
+
const edges = graph.edges.filter(e => e.type === 'extends').slice(0, 100);
|
|
235
|
+
const traversalTime = Date.now() - traversalStart;
|
|
236
|
+
|
|
237
|
+
this.results.gnn = {
|
|
238
|
+
graphStats: {
|
|
239
|
+
nodes: graph.nodes.length,
|
|
240
|
+
edges: graph.edges.length,
|
|
241
|
+
nodeTypes: [...new Set(graph.nodes.map(n => n.type))],
|
|
242
|
+
edgeTypes: [...new Set(graph.edges.map(e => e.type))]
|
|
243
|
+
},
|
|
244
|
+
performance: {
|
|
245
|
+
graphLoadTime: loadTime,
|
|
246
|
+
avgDependencyLookup: (depLatencies.reduce((a, b) => a + b, 0) / depLatencies.length).toFixed(2),
|
|
247
|
+
graphTraversalTime: traversalTime
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
console.log(` Graph nodes: ${graph.nodes.length}, edges: ${graph.edges.length}`);
|
|
252
|
+
console.log(` Graph load: ${loadTime}ms, dependency lookup: ${this.results.gnn.performance.avgDependencyLookup}ms`);
|
|
253
|
+
|
|
254
|
+
return this.results.gnn;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async generateReport() {
|
|
258
|
+
const report = `
|
|
259
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
260
|
+
ā MAGECTOR BENCHMARK REPORT ā
|
|
261
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
262
|
+
|
|
263
|
+
Generated: ${new Date().toISOString()}
|
|
264
|
+
|
|
265
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
266
|
+
INDEXING PERFORMANCE
|
|
267
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
268
|
+
${Object.entries(this.results.indexing).map(([name, data]) => `
|
|
269
|
+
${name}:
|
|
270
|
+
Duration: ${data.duration}ms
|
|
271
|
+
Files indexed: ${data.filesIndexed}
|
|
272
|
+
Files/second: ${data.filesPerSecond.toFixed(1)}
|
|
273
|
+
Memory delta: ${data.memoryUsed.toFixed(2)}MB
|
|
274
|
+
Graph nodes: ${data.graphNodes}
|
|
275
|
+
Graph edges: ${data.graphEdges}
|
|
276
|
+
`).join('')}
|
|
277
|
+
|
|
278
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
279
|
+
SEARCH PERFORMANCE
|
|
280
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
281
|
+
${Object.entries(this.results.search).map(([name, data]) => `
|
|
282
|
+
${name} (${data.iterations} iterations):
|
|
283
|
+
Min: ${data.min}ms
|
|
284
|
+
Max: ${data.max}ms
|
|
285
|
+
Average: ${data.avg}ms
|
|
286
|
+
P50: ${data.p50}ms
|
|
287
|
+
P90: ${data.p90}ms
|
|
288
|
+
P99: ${data.p99}ms
|
|
289
|
+
Throughput: ${data.throughput} queries/sec
|
|
290
|
+
`).join('')}
|
|
291
|
+
|
|
292
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
293
|
+
MEMORY USAGE
|
|
294
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
295
|
+
Baseline heap: ${this.results.memory.baseline.heapUsed}MB
|
|
296
|
+
After init: ${this.results.memory.afterInit.heapUsed}MB (+${this.results.memory.afterInit.delta}MB)
|
|
297
|
+
After indexing: ${this.results.memory.afterIndex.heapUsed}MB (+${this.results.memory.afterIndex.delta}MB)
|
|
298
|
+
After 100 searches: ${this.results.memory.afterSearch.heapUsed}MB (+${this.results.memory.afterSearch.delta}MB)
|
|
299
|
+
|
|
300
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
301
|
+
GNN FEATURES
|
|
302
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
303
|
+
Graph nodes: ${this.results.gnn.graphStats.nodes}
|
|
304
|
+
Graph edges: ${this.results.gnn.graphStats.edges}
|
|
305
|
+
Node types: ${this.results.gnn.graphStats.nodeTypes.join(', ')}
|
|
306
|
+
Edge types: ${this.results.gnn.graphStats.edgeTypes.join(', ')}
|
|
307
|
+
|
|
308
|
+
Graph load time: ${this.results.gnn.performance.graphLoadTime}ms
|
|
309
|
+
Avg dependency lookup: ${this.results.gnn.performance.avgDependencyLookup}ms
|
|
310
|
+
Graph traversal: ${this.results.gnn.performance.graphTraversalTime}ms
|
|
311
|
+
|
|
312
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
313
|
+
`;
|
|
314
|
+
|
|
315
|
+
await mkdir('./validation-results', { recursive: true });
|
|
316
|
+
await writeFile('./validation-results/benchmark-report.txt', report);
|
|
317
|
+
await writeFile('./validation-results/benchmark-results.json', JSON.stringify(this.results, null, 2));
|
|
318
|
+
|
|
319
|
+
return report;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async cleanup() {
|
|
323
|
+
if (existsSync(BENCHMARK_DIR)) {
|
|
324
|
+
await rm(BENCHMARK_DIR, { recursive: true });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async function runBenchmarks() {
|
|
330
|
+
const suite = new BenchmarkSuite();
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
334
|
+
console.log('ā MAGECTOR BENCHMARK SUITE ā');
|
|
335
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
336
|
+
|
|
337
|
+
await suite.setup(5);
|
|
338
|
+
await suite.benchmarkIndexing();
|
|
339
|
+
await suite.benchmarkSearch();
|
|
340
|
+
await suite.benchmarkMemory();
|
|
341
|
+
await suite.benchmarkGNN();
|
|
342
|
+
|
|
343
|
+
const report = await suite.generateReport();
|
|
344
|
+
console.log(report);
|
|
345
|
+
|
|
346
|
+
console.log('Results saved to:');
|
|
347
|
+
console.log(' - validation-results/benchmark-report.txt');
|
|
348
|
+
console.log(' - validation-results/benchmark-results.json\n');
|
|
349
|
+
|
|
350
|
+
} finally {
|
|
351
|
+
await suite.cleanup();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
runBenchmarks().catch(console.error);
|