magector 2.16.0 → 2.16.2
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/package.json +5 -5
- package/src/mcp-server.js +194 -61
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magector",
|
|
3
|
-
"version": "2.16.
|
|
3
|
+
"version": "2.16.2",
|
|
4
4
|
"description": "Semantic code search for Magento 2 — index, search, MCP server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/mcp-server.js",
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
"ruvector": "^0.1.96"
|
|
34
34
|
},
|
|
35
35
|
"optionalDependencies": {
|
|
36
|
-
"@magector/cli-darwin-arm64": "2.16.
|
|
37
|
-
"@magector/cli-linux-x64": "2.16.
|
|
38
|
-
"@magector/cli-linux-arm64": "2.16.
|
|
39
|
-
"@magector/cli-win32-x64": "2.16.
|
|
36
|
+
"@magector/cli-darwin-arm64": "2.16.2",
|
|
37
|
+
"@magector/cli-linux-x64": "2.16.2",
|
|
38
|
+
"@magector/cli-linux-arm64": "2.16.2",
|
|
39
|
+
"@magector/cli-win32-x64": "2.16.2"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
42
42
|
"magento",
|
package/src/mcp-server.js
CHANGED
|
@@ -423,6 +423,13 @@ let reindexInProgress = false;
|
|
|
423
423
|
let reindexProcess = null;
|
|
424
424
|
let warmupInProgress = true; // true until checkDbFormat + serve process ready
|
|
425
425
|
|
|
426
|
+
// Re-index progress tracking (updated from INDEX log lines)
|
|
427
|
+
let reindexStartTime = null;
|
|
428
|
+
let reindexPhase = 0; // 0=init, 1=AST, 2=embeddings, 3=HNSW
|
|
429
|
+
let reindexTotalFiles = 0;
|
|
430
|
+
let reindexItemsToEmbed = 0;
|
|
431
|
+
let reindexPhase2Start = null;
|
|
432
|
+
|
|
426
433
|
/**
|
|
427
434
|
* Check if the database file is compatible with the current binary.
|
|
428
435
|
* Uses a cached result to avoid running stats (30-60s) on every startup.
|
|
@@ -557,6 +564,11 @@ function startBackgroundReindex() {
|
|
|
557
564
|
}
|
|
558
565
|
|
|
559
566
|
reindexInProgress = true;
|
|
567
|
+
reindexStartTime = Date.now();
|
|
568
|
+
reindexPhase = 0;
|
|
569
|
+
reindexTotalFiles = 0;
|
|
570
|
+
reindexItemsToEmbed = 0;
|
|
571
|
+
reindexPhase2Start = null;
|
|
560
572
|
|
|
561
573
|
const hadExistingDb = existsSync(config.dbPath);
|
|
562
574
|
logToFile('WARN', `Starting background re-index to temp path. Old DB ${hadExistingDb ? 'preserved for queries' : 'not found'}.`);
|
|
@@ -590,18 +602,31 @@ function startBackgroundReindex() {
|
|
|
590
602
|
// entries arrive in large chunks instead of in real time.
|
|
591
603
|
const indexStdout = createInterface({ input: reindexProcess.stdout });
|
|
592
604
|
const indexStderr = createInterface({ input: reindexProcess.stderr });
|
|
605
|
+
const parseIndexProgress = (text) => {
|
|
606
|
+
const m = text.match(/Found (\d[\d,]+) files to index/);
|
|
607
|
+
if (m) reindexTotalFiles = parseInt(m[1].replace(/,/g, ''), 10);
|
|
608
|
+
if (text.includes('PHASE 1') || text.includes('AST analyzer')) reindexPhase = 1;
|
|
609
|
+
if (text.includes('PHASE 2') || text.includes('semantic embedding') || text.includes('Generating semantic')) {
|
|
610
|
+
if (reindexPhase < 2) { reindexPhase = 2; reindexPhase2Start = Date.now(); }
|
|
611
|
+
}
|
|
612
|
+
if (text.includes('PHASE 3') || text.includes('Building HNSW') || text.includes('HNSW')) reindexPhase = 3;
|
|
613
|
+
const em = text.match(/Items to embed: (\d[\d,]+)/);
|
|
614
|
+
if (em) reindexItemsToEmbed = parseInt(em[1].replace(/,/g, ''), 10);
|
|
615
|
+
};
|
|
593
616
|
indexStdout.on('line', (line) => {
|
|
594
617
|
const text = line.replace(/\x1b\[[0-9;]*m/g, '').trim();
|
|
595
|
-
if (text) logToFile('INDEX', text);
|
|
618
|
+
if (text) { logToFile('INDEX', text); parseIndexProgress(text); }
|
|
596
619
|
});
|
|
597
620
|
indexStderr.on('line', (line) => {
|
|
598
621
|
const text = line.replace(/\x1b\[[0-9;]*m/g, '').trim();
|
|
599
|
-
if (text) logToFile('INDEX', text);
|
|
622
|
+
if (text) { logToFile('INDEX', text); parseIndexProgress(text); }
|
|
600
623
|
});
|
|
601
624
|
|
|
602
625
|
reindexProcess.on('exit', (code) => {
|
|
603
626
|
reindexInProgress = false;
|
|
604
627
|
reindexProcess = null;
|
|
628
|
+
reindexStartTime = null;
|
|
629
|
+
reindexPhase = 0;
|
|
605
630
|
removeReindexPidFile();
|
|
606
631
|
if (code === 0) {
|
|
607
632
|
// Atomic swap: old → .bak, new → current
|
|
@@ -938,6 +963,10 @@ function tryConnectSocket() {
|
|
|
938
963
|
let globalServeQuery = null;
|
|
939
964
|
|
|
940
965
|
function serveQuery(command, params = {}, timeoutMs = 30000) {
|
|
966
|
+
if (!serveProcess || !serveReady) {
|
|
967
|
+
logToFile('WARN', `serveQuery(${command}): serve process not ready — returning error`);
|
|
968
|
+
return Promise.resolve({ ok: false, error: 'Serve process not ready' });
|
|
969
|
+
}
|
|
941
970
|
return new Promise((resolve, reject) => {
|
|
942
971
|
const id = serveNextId++;
|
|
943
972
|
logToFile('QUERY', `[${id}] → ${command}(${JSON.stringify(params).slice(0, 200)})`);
|
|
@@ -3631,7 +3660,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
3631
3660
|
tools: [
|
|
3632
3661
|
{
|
|
3633
3662
|
name: 'magento_search',
|
|
3634
|
-
description: 'Search Magento codebase semantically — find any PHP class, method, XML config, PHTML template, JS file, or GraphQL schema by describing what you need in natural language. Use this as a general-purpose search when no specialized tool fits. See also: magento_find_class, magento_find_method, magento_find_config for targeted searches.',
|
|
3663
|
+
description: 'Search Magento codebase semantically — find any PHP class, method, XML config, PHTML template, JS file, or GraphQL schema by describing what you need in natural language. Use this as a general-purpose search when no specialized tool fits. Works best for Magento core and popular vendor modules. For small/custom project-specific modules (e.g. proprietary modules not widely known to the embedding model), use magento_grep instead — semantic search may return 0 results for these. See also: magento_find_class, magento_find_method, magento_find_config for targeted searches.',
|
|
3635
3664
|
inputSchema: {
|
|
3636
3665
|
type: 'object',
|
|
3637
3666
|
properties: {
|
|
@@ -4278,7 +4307,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
4278
4307
|
},
|
|
4279
4308
|
{
|
|
4280
4309
|
name: 'magento_batch',
|
|
4281
|
-
description: 'Execute multiple Magector tool calls in a single request to reduce MCP round-trip overhead. Each query runs in parallel and returns combined results. Use this when you need 2+ independent lookups (e.g., find a class AND its plugins AND its observers in one call instead of three).',
|
|
4310
|
+
description: 'Execute multiple Magector tool calls in a single request to reduce MCP round-trip overhead. Each query runs in parallel and returns combined results. Use this when you need 2+ independent lookups (e.g., find a class AND its plugins AND its observers in one call instead of three). Supported tools: magento_search, magento_find_class, magento_find_method, magento_find_plugin, magento_find_observer, magento_find_config, magento_find_event_flow, magento_find_di_wiring, magento_find_callers, magento_find_preference, magento_find_fieldset, magento_module_structure, magento_trace_dependency, magento_impact_analysis, magento_grep, magento_read, magento_ast_search, magento_find_dataobject_issues, magento_find_null_risks.',
|
|
4282
4311
|
inputSchema: {
|
|
4283
4312
|
type: 'object',
|
|
4284
4313
|
properties: {
|
|
@@ -4391,7 +4420,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
4391
4420
|
},
|
|
4392
4421
|
{
|
|
4393
4422
|
name: 'magento_find_null_risks',
|
|
4394
|
-
description: 'Find method chains without null guards using the pre-built enrichment index. Returns all ->firstMethod()->secondMethod() calls where no null check (=== null, !== null, ?->, ??, isset, is_null) was detected in surrounding code. Requires magento_enrich to have been run first. 100× faster than grep — O(1) SQLite query vs O(n) file scan. Use firstMethod to filter (e.g., "getPayment" finds all ->getPayment()->anything() without null guard). ⚡ For multi-query workflows use magento_batch.',
|
|
4423
|
+
description: 'Find method chains without null guards using the pre-built enrichment index. Returns all ->firstMethod()->secondMethod() calls where no null check (=== null, !== null, ?->, ??, isset, is_null) was detected in surrounding code. Requires magento_enrich to have been run first (magento_index triggers it automatically in the background). 100× faster than grep — O(1) SQLite query vs O(n) file scan. Use firstMethod to filter (e.g., "getPayment" finds all ->getPayment()->anything() without null guard). ⚡ For multi-query workflows use magento_batch.',
|
|
4395
4424
|
inputSchema: {
|
|
4396
4425
|
type: 'object',
|
|
4397
4426
|
properties: {
|
|
@@ -4458,7 +4487,49 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
4458
4487
|
]
|
|
4459
4488
|
}));
|
|
4460
4489
|
|
|
4461
|
-
|
|
4490
|
+
/**
|
|
4491
|
+
* Build a reindex warning string for tool responses during background re-index.
|
|
4492
|
+
* Returns null when not re-indexing.
|
|
4493
|
+
*/
|
|
4494
|
+
function getReindexWarning() {
|
|
4495
|
+
if (!reindexInProgress || !reindexStartTime) return null;
|
|
4496
|
+
const elapsedSec = Math.round((Date.now() - reindexStartTime) / 1000);
|
|
4497
|
+
const elapsedStr = elapsedSec >= 60
|
|
4498
|
+
? `${Math.floor(elapsedSec / 60)}m ${elapsedSec % 60}s`
|
|
4499
|
+
: `${elapsedSec}s`;
|
|
4500
|
+
|
|
4501
|
+
let phaseStr, etaStr;
|
|
4502
|
+
if (reindexPhase <= 0) {
|
|
4503
|
+
phaseStr = 'initializing';
|
|
4504
|
+
etaStr = 'est. ~40–70 min total';
|
|
4505
|
+
} else if (reindexPhase === 1) {
|
|
4506
|
+
const filesStr = reindexTotalFiles > 0 ? ` (${reindexTotalFiles.toLocaleString('en')} files)` : '';
|
|
4507
|
+
phaseStr = `phase 1/3: AST parsing${filesStr}`;
|
|
4508
|
+
etaStr = 'est. ~1–3 min for this phase, then ~40–60 min for embeddings';
|
|
4509
|
+
} else if (reindexPhase === 2) {
|
|
4510
|
+
const itemsStr = reindexItemsToEmbed > 0 ? ` (${reindexItemsToEmbed.toLocaleString('en')} items)` : '';
|
|
4511
|
+
phaseStr = `phase 2/3: generating embeddings${itemsStr}`;
|
|
4512
|
+
if (reindexPhase2Start && reindexItemsToEmbed > 0) {
|
|
4513
|
+
// Empirical rate: ~87k items ≈ 45 min on 8-core ONNX. Scale linearly.
|
|
4514
|
+
const estimatedTotalSec = Math.round((reindexItemsToEmbed / 87000) * 45 * 60);
|
|
4515
|
+
const phase2Elapsed = (Date.now() - reindexPhase2Start) / 1000;
|
|
4516
|
+
const remainingSec = Math.max(estimatedTotalSec - phase2Elapsed, 0);
|
|
4517
|
+
etaStr = remainingSec > 60
|
|
4518
|
+
? `est. ~${Math.round(remainingSec / 60)} min remaining`
|
|
4519
|
+
: 'almost done with embeddings';
|
|
4520
|
+
} else {
|
|
4521
|
+
etaStr = 'est. 30–60 min for this phase';
|
|
4522
|
+
}
|
|
4523
|
+
} else {
|
|
4524
|
+
phaseStr = 'phase 3/3: building HNSW vector index';
|
|
4525
|
+
etaStr = 'est. ~5–10 min remaining';
|
|
4526
|
+
}
|
|
4527
|
+
|
|
4528
|
+
return `> ⏳ **Re-indexing in progress** — ${elapsedStr} elapsed, ${phaseStr}. ${etaStr}.\n` +
|
|
4529
|
+
`> Results below use the **previous index** — valid, but may miss recently added files.\n\n`;
|
|
4530
|
+
}
|
|
4531
|
+
|
|
4532
|
+
const _callToolHandler = async (request) => {
|
|
4462
4533
|
const { name, arguments: args } = request.params;
|
|
4463
4534
|
const reqStart = Date.now();
|
|
4464
4535
|
logToFile('REQ', `${name}(${JSON.stringify(args || {})})`);
|
|
@@ -5200,43 +5271,63 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
5200
5271
|
}
|
|
5201
5272
|
|
|
5202
5273
|
case 'magento_module_structure': {
|
|
5203
|
-
const raw = await rustSearchAsync(args.moduleName, 200);
|
|
5204
|
-
// Support both app/code (Magento/Catalog/) and vendor (module-catalog/) paths
|
|
5205
|
-
const modulePath = args.moduleName.replace('_', '/') + '/';
|
|
5206
5274
|
const parts = args.moduleName.split('_');
|
|
5275
|
+
// Support both app/code (Magento/Catalog/) and vendor (magento/module-catalog/) paths
|
|
5276
|
+
const modulePath = args.moduleName.replace('_', '/') + '/';
|
|
5207
5277
|
// Hyphenate camelCase for vendor path: OrderSplit → order-split
|
|
5278
|
+
const vendorDir = parts.length === 2 ? parts[0].toLowerCase() : '';
|
|
5208
5279
|
const vendorPath = parts.length === 2
|
|
5209
5280
|
? `module-${parts[1].replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}/`
|
|
5210
5281
|
: '';
|
|
5211
|
-
let results =
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
logToFile('INFO', `module_structure: vector search returned 0 results for "${args.moduleName}" — using filesystem fallback (glob ${vendorPath})`);
|
|
5223
|
-
try {
|
|
5224
|
-
const vendorGlob = `**/${vendorPath}**/*.{php,xml,phtml}`;
|
|
5225
|
-
const files = await glob(vendorGlob, { cwd: config.magentoRoot, absolute: false, nodir: true });
|
|
5226
|
-
for (const f of files.slice(0, 100)) {
|
|
5227
|
-
const entry = { path: f, score: 0.5 };
|
|
5228
|
-
if (f.includes('/Controller/')) entry.isController = true;
|
|
5229
|
-
if (f.includes('/Model/')) entry.isModel = true;
|
|
5230
|
-
if (f.includes('/Block/')) entry.isBlock = true;
|
|
5231
|
-
if (f.includes('/Plugin/')) entry.isPlugin = true;
|
|
5232
|
-
if (f.includes('/Observer/')) entry.isObserver = true;
|
|
5233
|
-
if (f.endsWith('.xml')) entry.type = 'xml';
|
|
5234
|
-
// Extract class name from path
|
|
5235
|
-
const phpMatch = f.match(/\/([A-Z]\w+)\.php$/);
|
|
5236
|
-
if (phpMatch) entry.className = phpMatch[1];
|
|
5237
|
-
results.push(entry);
|
|
5282
|
+
let results = [];
|
|
5283
|
+
|
|
5284
|
+
// Primary: filesystem-based (authoritative — avoids mixing cross-references from vector search)
|
|
5285
|
+
if (config.magentoRoot) {
|
|
5286
|
+
const fsGlobs = [];
|
|
5287
|
+
if (parts.length === 2) {
|
|
5288
|
+
// app/code/{Vendor}/{Module}/
|
|
5289
|
+
fsGlobs.push(`app/code/${parts[0]}/${parts[1]}/**/*.{php,xml,phtml}`);
|
|
5290
|
+
// vendor/{vendor-lower}/{module-lower}/ — vendor-specific to avoid false positives
|
|
5291
|
+
if (vendorDir && vendorPath) {
|
|
5292
|
+
fsGlobs.push(`vendor/${vendorDir}/${vendorPath}**/*.{php,xml,phtml}`);
|
|
5238
5293
|
}
|
|
5239
|
-
}
|
|
5294
|
+
}
|
|
5295
|
+
for (const globPattern of fsGlobs) {
|
|
5296
|
+
try {
|
|
5297
|
+
const files = await glob(globPattern, { cwd: config.magentoRoot, absolute: false, nodir: true });
|
|
5298
|
+
if (files.length > 0) {
|
|
5299
|
+
logToFile('INFO', `module_structure: filesystem found ${files.length} files for "${args.moduleName}" (${globPattern})`);
|
|
5300
|
+
for (const f of files.slice(0, 100)) {
|
|
5301
|
+
const entry = { path: f, score: 1.0 };
|
|
5302
|
+
if (f.includes('/Controller/')) entry.isController = true;
|
|
5303
|
+
if (f.includes('/Model/')) entry.isModel = true;
|
|
5304
|
+
if (f.includes('/Block/')) entry.isBlock = true;
|
|
5305
|
+
if (f.includes('/Plugin/')) entry.isPlugin = true;
|
|
5306
|
+
if (f.includes('/Observer/')) entry.isObserver = true;
|
|
5307
|
+
if (f.endsWith('.xml')) entry.type = 'xml';
|
|
5308
|
+
const phpMatch = f.match(/\/([A-Z]\w+)\.php$/);
|
|
5309
|
+
if (phpMatch) entry.className = phpMatch[1];
|
|
5310
|
+
results.push(entry);
|
|
5311
|
+
}
|
|
5312
|
+
break; // Found in one location, stop
|
|
5313
|
+
}
|
|
5314
|
+
} catch {}
|
|
5315
|
+
}
|
|
5316
|
+
}
|
|
5317
|
+
|
|
5318
|
+
// Fallback: vector search with strict path/module filtering (only if filesystem found nothing)
|
|
5319
|
+
if (results.length === 0) {
|
|
5320
|
+
logToFile('INFO', `module_structure: filesystem found 0 files for "${args.moduleName}" — falling back to vector search`);
|
|
5321
|
+
const raw = await rustSearchAsync(args.moduleName, 200);
|
|
5322
|
+
results = raw.map(normalizeResult).filter(r => {
|
|
5323
|
+
const p = r.path || '';
|
|
5324
|
+
const mod = r.module || '';
|
|
5325
|
+
// Exact module match or directory-level path match (trailing slash prevents Catalog matching CatalogRule)
|
|
5326
|
+
return mod === args.moduleName ||
|
|
5327
|
+
p.includes(modulePath) ||
|
|
5328
|
+
// Vendor-specific path check to avoid matching other vendors' same-named modules
|
|
5329
|
+
(vendorDir && vendorPath && p.toLowerCase().includes(`${vendorDir}/${vendorPath}`));
|
|
5330
|
+
});
|
|
5240
5331
|
}
|
|
5241
5332
|
|
|
5242
5333
|
const structure = {
|
|
@@ -6182,6 +6273,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
6182
6273
|
}
|
|
6183
6274
|
break;
|
|
6184
6275
|
}
|
|
6276
|
+
case 'magento_find_config': {
|
|
6277
|
+
let cfgQuery = a.query;
|
|
6278
|
+
if (a.configType && a.configType !== 'other') cfgQuery = `${a.configType}.xml xml config ${a.query}`;
|
|
6279
|
+
const cfgRaw = await rustSearchAsync(cfgQuery, 100);
|
|
6280
|
+
let cfgRes = cfgRaw.map(normalizeResult).filter(r =>
|
|
6281
|
+
r.type === 'xml' || r.path?.endsWith('.xml') || r.path?.includes('.xml')
|
|
6282
|
+
);
|
|
6283
|
+
if (a.configType && a.configType !== 'other') {
|
|
6284
|
+
const cfgTypeFile = `${a.configType}.xml`;
|
|
6285
|
+
const cfgTyped = cfgRes.filter(r => r.path?.includes(cfgTypeFile));
|
|
6286
|
+
if (cfgTyped.length >= 3) cfgRes = cfgTyped;
|
|
6287
|
+
}
|
|
6288
|
+
text = formatSearchResults(cfgRes.slice(0, 10));
|
|
6289
|
+
break;
|
|
6290
|
+
}
|
|
6185
6291
|
case 'magento_trace_dependency': {
|
|
6186
6292
|
const dep = await traceDependency(a.className, a.direction || 'both');
|
|
6187
6293
|
text = `Preferences: ${dep.preferences.length}, Plugins: ${dep.plugins.length}, VirtualTypes: ${dep.virtualTypes.length}, Args: ${dep.argumentOverrides.length}\n`;
|
|
@@ -6271,35 +6377,52 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
6271
6377
|
break;
|
|
6272
6378
|
}
|
|
6273
6379
|
case 'magento_module_structure': {
|
|
6274
|
-
const raw = await rustSearchAsync(a.moduleName, 200);
|
|
6275
|
-
const modulePath = a.moduleName.replace('_', '/') + '/';
|
|
6276
6380
|
const mParts = a.moduleName.split('_');
|
|
6277
|
-
|
|
6381
|
+
const modulePath = a.moduleName.replace('_', '/') + '/';
|
|
6382
|
+
const mVendorDir = mParts.length === 2 ? mParts[0].toLowerCase() : '';
|
|
6278
6383
|
const vendorPath = mParts.length === 2
|
|
6279
6384
|
? `module-${mParts[1].replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}/`
|
|
6280
6385
|
: '';
|
|
6281
|
-
let res =
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
const vendorGlob = `**/${vendorPath}**/*.{php,xml,phtml}`;
|
|
6290
|
-
const files = await glob(vendorGlob, { cwd: config.magentoRoot, absolute: false, nodir: true });
|
|
6291
|
-
for (const f of files.slice(0, 100)) {
|
|
6292
|
-
const entry = { path: f, score: 0.5 };
|
|
6293
|
-
if (f.includes('/Controller/')) entry.isController = true;
|
|
6294
|
-
if (f.includes('/Model/')) entry.isModel = true;
|
|
6295
|
-
if (f.includes('/Plugin/')) entry.isPlugin = true;
|
|
6296
|
-
if (f.includes('/Observer/')) entry.isObserver = true;
|
|
6297
|
-
if (f.endsWith('.xml')) entry.type = 'xml';
|
|
6298
|
-
const phpMatch = f.match(/\/([A-Z]\w+)\.php$/);
|
|
6299
|
-
if (phpMatch) entry.className = phpMatch[1];
|
|
6300
|
-
res.push(entry);
|
|
6386
|
+
let res = [];
|
|
6387
|
+
// Primary: filesystem-based (avoids mixing cross-references)
|
|
6388
|
+
if (config.magentoRoot) {
|
|
6389
|
+
const msGlobs = [];
|
|
6390
|
+
if (mParts.length === 2) {
|
|
6391
|
+
msGlobs.push(`app/code/${mParts[0]}/${mParts[1]}/**/*.{php,xml,phtml}`);
|
|
6392
|
+
if (mVendorDir && vendorPath) {
|
|
6393
|
+
msGlobs.push(`vendor/${mVendorDir}/${vendorPath}**/*.{php,xml,phtml}`);
|
|
6301
6394
|
}
|
|
6302
|
-
}
|
|
6395
|
+
}
|
|
6396
|
+
for (const gp of msGlobs) {
|
|
6397
|
+
try {
|
|
6398
|
+
const files = await glob(gp, { cwd: config.magentoRoot, absolute: false, nodir: true });
|
|
6399
|
+
if (files.length > 0) {
|
|
6400
|
+
for (const f of files.slice(0, 100)) {
|
|
6401
|
+
const entry = { path: f, score: 1.0 };
|
|
6402
|
+
if (f.includes('/Controller/')) entry.isController = true;
|
|
6403
|
+
if (f.includes('/Model/')) entry.isModel = true;
|
|
6404
|
+
if (f.includes('/Plugin/')) entry.isPlugin = true;
|
|
6405
|
+
if (f.includes('/Observer/')) entry.isObserver = true;
|
|
6406
|
+
if (f.endsWith('.xml')) entry.type = 'xml';
|
|
6407
|
+
const phpMatch = f.match(/\/([A-Z]\w+)\.php$/);
|
|
6408
|
+
if (phpMatch) entry.className = phpMatch[1];
|
|
6409
|
+
res.push(entry);
|
|
6410
|
+
}
|
|
6411
|
+
break;
|
|
6412
|
+
}
|
|
6413
|
+
} catch {}
|
|
6414
|
+
}
|
|
6415
|
+
}
|
|
6416
|
+
// Fallback: vector search with vendor-specific path filtering
|
|
6417
|
+
if (res.length === 0) {
|
|
6418
|
+
const raw = await rustSearchAsync(a.moduleName, 200);
|
|
6419
|
+
res = raw.map(normalizeResult).filter(r => {
|
|
6420
|
+
const p = r.path || '';
|
|
6421
|
+
const mod = r.module || '';
|
|
6422
|
+
return mod === a.moduleName ||
|
|
6423
|
+
p.includes(modulePath) ||
|
|
6424
|
+
(mVendorDir && vendorPath && p.toLowerCase().includes(`${mVendorDir}/${vendorPath}`));
|
|
6425
|
+
});
|
|
6303
6426
|
}
|
|
6304
6427
|
text = `Module: ${a.moduleName} (${res.length} files)\n`;
|
|
6305
6428
|
const cats = { controllers: '/Controller/', models: '/Model/', plugins: '/Plugin/', observers: '/Observer/', api: '/Api/' };
|
|
@@ -6872,6 +6995,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
6872
6995
|
serveQuery('feedback', { signals }).catch((err) => logToFile('WARN', `Feedback signal send failed: ${err.message}`));
|
|
6873
6996
|
}
|
|
6874
6997
|
}
|
|
6998
|
+
};
|
|
6999
|
+
|
|
7000
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
7001
|
+
const result = await _callToolHandler(request);
|
|
7002
|
+
// Append re-index warning to non-error responses during background re-index
|
|
7003
|
+
if (reindexInProgress && !result?.isError && result?.content?.[0]?.type === 'text') {
|
|
7004
|
+
const warning = getReindexWarning();
|
|
7005
|
+
if (warning) result.content[0].text = warning + result.content[0].text;
|
|
7006
|
+
}
|
|
7007
|
+
return result;
|
|
6875
7008
|
});
|
|
6876
7009
|
|
|
6877
7010
|
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|