magector 2.5.1 → 2.6.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/package.json +5 -5
- package/src/mcp-server.js +174 -35
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magector",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
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.
|
|
37
|
-
"@magector/cli-linux-x64": "2.
|
|
38
|
-
"@magector/cli-linux-arm64": "2.
|
|
39
|
-
"@magector/cli-win32-x64": "2.
|
|
36
|
+
"@magector/cli-darwin-arm64": "2.6.0",
|
|
37
|
+
"@magector/cli-linux-x64": "2.6.0",
|
|
38
|
+
"@magector/cli-linux-arm64": "2.6.0",
|
|
39
|
+
"@magector/cli-win32-x64": "2.6.0"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
42
42
|
"magento",
|
package/src/mcp-server.js
CHANGED
|
@@ -1004,6 +1004,41 @@ function readMethodSnippet(filePath, methodName, maxLines = 15) {
|
|
|
1004
1004
|
return null;
|
|
1005
1005
|
}
|
|
1006
1006
|
|
|
1007
|
+
/**
|
|
1008
|
+
* Read the FULL method body using brace-counting to find the closing brace.
|
|
1009
|
+
* Unlike readMethodSnippet (fixed N lines), this extracts the complete method
|
|
1010
|
+
* regardless of length, up to a safety limit.
|
|
1011
|
+
* @param {string} filePath - absolute or relative PHP file path
|
|
1012
|
+
* @param {string} methodName - method name to find
|
|
1013
|
+
* @param {number} maxLines - safety limit to prevent reading huge methods (default: 60)
|
|
1014
|
+
* @returns {string|null} complete method source or null if not found
|
|
1015
|
+
*/
|
|
1016
|
+
function readFullMethodBody(filePath, methodName, maxLines = 60) {
|
|
1017
|
+
const absPath = filePath.startsWith('/') ? filePath : path.join(config.magentoRoot, filePath);
|
|
1018
|
+
let content;
|
|
1019
|
+
try { content = readFileSync(absPath, 'utf-8'); } catch { return null; }
|
|
1020
|
+
const lines = content.split('\n');
|
|
1021
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1022
|
+
if (lines[i].includes(`function ${methodName}(`)) {
|
|
1023
|
+
// Use brace counting to find the complete method body
|
|
1024
|
+
let braceCount = 0;
|
|
1025
|
+
let started = false;
|
|
1026
|
+
for (let j = i; j < lines.length && j < i + maxLines; j++) {
|
|
1027
|
+
for (const ch of lines[j]) {
|
|
1028
|
+
if (ch === '{') { braceCount++; started = true; }
|
|
1029
|
+
if (ch === '}') braceCount--;
|
|
1030
|
+
}
|
|
1031
|
+
if (started && braceCount <= 0) {
|
|
1032
|
+
return lines.slice(i, j + 1).join('\n');
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
// Safety: if closing brace not found within maxLines, return what we have
|
|
1036
|
+
return lines.slice(i, Math.min(i + maxLines, lines.length)).join('\n');
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
return null;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1007
1042
|
/**
|
|
1008
1043
|
* Parse all fieldset.xml files in the Magento root.
|
|
1009
1044
|
* Returns array of { file, scope, fieldset, fields: [{ field, aspect }] }.
|
|
@@ -1561,15 +1596,23 @@ function formatSearchResults(results) {
|
|
|
1561
1596
|
if (r.isBlock) badges.push('block');
|
|
1562
1597
|
if (badges.length > 0) entry.badges = badges;
|
|
1563
1598
|
|
|
1599
|
+
// Only include verbose content (snippet, codePreview) for top-ranked results
|
|
1600
|
+
// to reduce token consumption — lower-ranked results just show metadata
|
|
1601
|
+
const isTopRanked = i < 3;
|
|
1602
|
+
|
|
1564
1603
|
// Snippet — first 300 chars of indexed content for quick assessment
|
|
1565
|
-
if (r.searchText) {
|
|
1604
|
+
if (isTopRanked && r.searchText) {
|
|
1566
1605
|
entry.snippet = r.searchText.length > 300
|
|
1567
1606
|
? r.searchText.slice(0, 300) + '...'
|
|
1568
1607
|
: r.searchText;
|
|
1569
1608
|
}
|
|
1570
1609
|
|
|
1610
|
+
// Full method body — attached by magento_find_method for complete understanding
|
|
1611
|
+
if (r.fullMethodBody) {
|
|
1612
|
+
entry.codePreview = r.fullMethodBody;
|
|
1613
|
+
}
|
|
1571
1614
|
// Code preview — read actual source lines for PHP files with known class/method
|
|
1572
|
-
if (r.path && (r.path.endsWith('.php') || r.path.endsWith('.phtml'))) {
|
|
1615
|
+
else if (isTopRanked && r.path && (r.path.endsWith('.php') || r.path.endsWith('.phtml'))) {
|
|
1573
1616
|
if (r.methodName) {
|
|
1574
1617
|
const preview = readMethodSnippet(r.path, r.methodName, 10);
|
|
1575
1618
|
if (preview) entry.codePreview = preview;
|
|
@@ -1600,6 +1643,47 @@ function formatSearchResults(results) {
|
|
|
1600
1643
|
return JSON.stringify({ results: formatted, count: formatted.length });
|
|
1601
1644
|
}
|
|
1602
1645
|
|
|
1646
|
+
// ─── DI XML Session Cache ─────────────────────────────────────
|
|
1647
|
+
|
|
1648
|
+
/**
|
|
1649
|
+
* Session-level cache for parsed di.xml file contents.
|
|
1650
|
+
* Avoids re-reading and re-globbing di.xml files across multiple tool calls
|
|
1651
|
+
* (findDiWiring, traceDependency, magento_find_plugin all scan di.xml).
|
|
1652
|
+
*/
|
|
1653
|
+
const diXmlCache = {
|
|
1654
|
+
/** @type {Map<string, string>} path → file content */
|
|
1655
|
+
files: new Map(),
|
|
1656
|
+
/** @type {string[]|null} cached list of all di.xml absolute paths */
|
|
1657
|
+
paths: null,
|
|
1658
|
+
/** @type {string|null} root used for caching (invalidate if root changes) */
|
|
1659
|
+
root: null
|
|
1660
|
+
};
|
|
1661
|
+
|
|
1662
|
+
/**
|
|
1663
|
+
* Get all di.xml file paths and their contents, using session cache.
|
|
1664
|
+
* @param {string} root - Magento root path
|
|
1665
|
+
* @returns {Promise<Array<{absPath: string, relPath: string, content: string}>>}
|
|
1666
|
+
*/
|
|
1667
|
+
async function getDiXmlFiles(root) {
|
|
1668
|
+
if (diXmlCache.root !== root || !diXmlCache.paths) {
|
|
1669
|
+
diXmlCache.root = root;
|
|
1670
|
+
diXmlCache.paths = await glob('**/etc/**/di.xml', { cwd: root, absolute: true, nodir: true });
|
|
1671
|
+
diXmlCache.files.clear();
|
|
1672
|
+
}
|
|
1673
|
+
const results = [];
|
|
1674
|
+
for (const absPath of diXmlCache.paths) {
|
|
1675
|
+
let content = diXmlCache.files.get(absPath);
|
|
1676
|
+
if (content === undefined) {
|
|
1677
|
+
try { content = readFileSync(absPath, 'utf-8'); } catch { content = null; }
|
|
1678
|
+
diXmlCache.files.set(absPath, content);
|
|
1679
|
+
}
|
|
1680
|
+
if (content !== null) {
|
|
1681
|
+
results.push({ absPath, relPath: absPath.replace(root + '/', ''), content });
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
return results;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1603
1687
|
// ─── DI Dependency Tracing ─────────────────────────────────────
|
|
1604
1688
|
|
|
1605
1689
|
/**
|
|
@@ -1608,7 +1692,7 @@ function formatSearchResults(results) {
|
|
|
1608
1692
|
*/
|
|
1609
1693
|
async function traceDependency(className, direction = 'both') {
|
|
1610
1694
|
const root = config.magentoRoot;
|
|
1611
|
-
const diFiles = await
|
|
1695
|
+
const diFiles = await getDiXmlFiles(root);
|
|
1612
1696
|
const classLower = className.toLowerCase();
|
|
1613
1697
|
const classShort = className.split('\\').pop().toLowerCase();
|
|
1614
1698
|
|
|
@@ -1621,13 +1705,7 @@ async function traceDependency(className, direction = 'both') {
|
|
|
1621
1705
|
totalDiFiles: diFiles.length
|
|
1622
1706
|
};
|
|
1623
1707
|
|
|
1624
|
-
for (const
|
|
1625
|
-
let content;
|
|
1626
|
-
try {
|
|
1627
|
-
content = readFileSync(diFile, 'utf-8');
|
|
1628
|
-
} catch { continue; }
|
|
1629
|
-
|
|
1630
|
-
const relativePath = diFile.replace(root + '/', '');
|
|
1708
|
+
for (const { content, relPath: relativePath } of diFiles) {
|
|
1631
1709
|
|
|
1632
1710
|
if (direction === 'resolve' || direction === 'both') {
|
|
1633
1711
|
// Find preferences: <preference for="ClassName" type="Implementation"/>
|
|
@@ -2553,6 +2631,21 @@ async function findDiWiring(className) {
|
|
|
2553
2631
|
const root = config.magentoRoot;
|
|
2554
2632
|
const shortName = className.split('\\').pop();
|
|
2555
2633
|
const shortLower = shortName.toLowerCase();
|
|
2634
|
+
// When a FQCN is provided (contains \), use it for precise matching
|
|
2635
|
+
const hasFqcn = className.includes('\\');
|
|
2636
|
+
// Normalize FQCN for XML matching: di.xml uses backslash-escaped names
|
|
2637
|
+
const fqcnNormalized = hasFqcn ? className.replace(/\\\\/g, '\\') : null;
|
|
2638
|
+
const fqcnLower = fqcnNormalized ? fqcnNormalized.toLowerCase() : null;
|
|
2639
|
+
|
|
2640
|
+
// Helper: check if a di.xml class name matches the requested class.
|
|
2641
|
+
// When FQCN is available, require full namespace match; otherwise fall back to short name.
|
|
2642
|
+
function matchesClass(xmlClassName) {
|
|
2643
|
+
const xmlLower = xmlClassName.toLowerCase().replace(/\\\\/g, '\\');
|
|
2644
|
+
if (fqcnLower) {
|
|
2645
|
+
return xmlLower === fqcnLower || xmlLower.endsWith('\\' + fqcnLower);
|
|
2646
|
+
}
|
|
2647
|
+
return xmlLower.includes(shortLower);
|
|
2648
|
+
}
|
|
2556
2649
|
|
|
2557
2650
|
const result = {
|
|
2558
2651
|
className,
|
|
@@ -2564,24 +2657,20 @@ async function findDiWiring(className) {
|
|
|
2564
2657
|
totalDiFiles: 0
|
|
2565
2658
|
};
|
|
2566
2659
|
|
|
2567
|
-
const diFiles = await
|
|
2660
|
+
const diFiles = await getDiXmlFiles(root);
|
|
2568
2661
|
result.totalDiFiles = diFiles.length;
|
|
2569
2662
|
|
|
2570
|
-
for (const
|
|
2571
|
-
let content;
|
|
2572
|
-
try { content = readFileSync(diFile, 'utf-8'); } catch { continue; }
|
|
2573
|
-
const relativePath = diFile.replace(root + '/', '');
|
|
2663
|
+
for (const { content, relPath: relativePath } of diFiles) {
|
|
2574
2664
|
const contentLower = content.toLowerCase();
|
|
2575
2665
|
|
|
2666
|
+
// Quick pre-filter: skip files that don't contain the short name at all
|
|
2576
2667
|
if (!contentLower.includes(shortLower)) continue;
|
|
2577
2668
|
|
|
2578
2669
|
// 1. Preferences where this class is the "for" or "type"
|
|
2579
2670
|
const prefRegex = /<preference\s+for="([^"]+)"\s+type="([^"]+)"\s*\/?>/g;
|
|
2580
2671
|
let m;
|
|
2581
2672
|
while ((m = prefRegex.exec(content)) !== null) {
|
|
2582
|
-
|
|
2583
|
-
const typeLower = m[2].toLowerCase();
|
|
2584
|
-
if (forLower.includes(shortLower) || typeLower.includes(shortLower)) {
|
|
2673
|
+
if (matchesClass(m[1]) || matchesClass(m[2])) {
|
|
2585
2674
|
result.preferences.push({ for: m[1], type: m[2], file: relativePath });
|
|
2586
2675
|
}
|
|
2587
2676
|
}
|
|
@@ -2591,10 +2680,9 @@ async function findDiWiring(className) {
|
|
|
2591
2680
|
let typeMatch;
|
|
2592
2681
|
while ((typeMatch = typeBlockRegex.exec(content)) !== null) {
|
|
2593
2682
|
const typeName = typeMatch[1];
|
|
2594
|
-
const typeNameLower = typeName.toLowerCase();
|
|
2595
2683
|
const typeBlock = typeMatch[2];
|
|
2596
2684
|
|
|
2597
|
-
if (!
|
|
2685
|
+
if (!matchesClass(typeName)) continue;
|
|
2598
2686
|
|
|
2599
2687
|
// Plugins
|
|
2600
2688
|
const pluginRegex = /<plugin\s+name="([^"]+)"[^>]*type="([^"]+)"[^>]*/g;
|
|
@@ -2641,7 +2729,7 @@ async function findDiWiring(className) {
|
|
|
2641
2729
|
const vtRegex = /<virtualType\s+name="([^"]+)"[^>]*type="([^"]+)"[^>]*(?:\/>|>([\s\S]*?)<\/virtualType>)/g;
|
|
2642
2730
|
while ((m = vtRegex.exec(content)) !== null) {
|
|
2643
2731
|
const vtType = m[2];
|
|
2644
|
-
if (!vtType
|
|
2732
|
+
if (!matchesClass(vtType)) continue;
|
|
2645
2733
|
const vtEntry = { name: m[1], type: vtType, file: relativePath };
|
|
2646
2734
|
if (m[3]) {
|
|
2647
2735
|
const argRegex = /<argument\s+name="([^"]+)"[^>]*xsi:type="([^"]+)"[^>]*>([^<]*)<\/argument>/g;
|
|
@@ -2657,6 +2745,7 @@ async function findDiWiring(className) {
|
|
|
2657
2745
|
}
|
|
2658
2746
|
|
|
2659
2747
|
// 4. Constructor arguments from PHP class
|
|
2748
|
+
// When FQCN is provided, verify namespace matches to avoid class name collisions
|
|
2660
2749
|
const phpFiles = await glob(`**/${shortName}.php`, { cwd: root, absolute: true, nodir: true });
|
|
2661
2750
|
for (const phpFile of phpFiles) {
|
|
2662
2751
|
let content;
|
|
@@ -2664,6 +2753,17 @@ async function findDiWiring(className) {
|
|
|
2664
2753
|
const classMatch = content.match(/(?:class|abstract\s+class)\s+(\w+)/);
|
|
2665
2754
|
if (!classMatch || classMatch[1] !== shortName) continue;
|
|
2666
2755
|
|
|
2756
|
+
// FQCN namespace verification: when a full class name was provided,
|
|
2757
|
+
// check that the PHP file's namespace matches to avoid collisions
|
|
2758
|
+
// (e.g., two different ViewPlugin classes in different modules)
|
|
2759
|
+
if (fqcnNormalized) {
|
|
2760
|
+
const nsMatch = content.match(/namespace\s+([\w\\]+)\s*;/);
|
|
2761
|
+
if (nsMatch) {
|
|
2762
|
+
const fileFqcn = (nsMatch[1] + '\\' + shortName).toLowerCase();
|
|
2763
|
+
if (fileFqcn !== fqcnLower) continue; // Wrong class, skip
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2667
2767
|
const ctorMatch = content.match(/function\s+__construct\s*\(([\s\S]*?)\)\s*[{:]/);
|
|
2668
2768
|
if (ctorMatch) {
|
|
2669
2769
|
const paramRegex = /(?:([\w\\]+)\s+)?(\$\w+)/g;
|
|
@@ -2672,6 +2772,7 @@ async function findDiWiring(className) {
|
|
|
2672
2772
|
result.constructorArguments.push({ typeHint: pm[1] || null, variable: pm[2] });
|
|
2673
2773
|
}
|
|
2674
2774
|
}
|
|
2775
|
+
result.constructorSourceFile = phpFile.replace(root + '/', '');
|
|
2675
2776
|
break;
|
|
2676
2777
|
}
|
|
2677
2778
|
|
|
@@ -3946,7 +4047,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3946
4047
|
return {
|
|
3947
4048
|
content: [{
|
|
3948
4049
|
type: 'text',
|
|
3949
|
-
text: formatSearchResults(results.slice(0, args.limit ||
|
|
4050
|
+
text: formatSearchResults(results.slice(0, args.limit || 5))
|
|
3950
4051
|
}]
|
|
3951
4052
|
};
|
|
3952
4053
|
}
|
|
@@ -3963,7 +4064,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3963
4064
|
return {
|
|
3964
4065
|
content: [{
|
|
3965
4066
|
type: 'text',
|
|
3966
|
-
text: formatSearchResults(results.slice(0,
|
|
4067
|
+
text: formatSearchResults(results.slice(0, 3))
|
|
3967
4068
|
}]
|
|
3968
4069
|
};
|
|
3969
4070
|
}
|
|
@@ -3992,10 +4093,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3992
4093
|
if (r.methodName?.toLowerCase() === methodLower) bonus += 0.3;
|
|
3993
4094
|
return { ...r, score: (r.score || 0) + bonus };
|
|
3994
4095
|
}).sort((a, b) => b.score - a.score);
|
|
4096
|
+
// Attach full method body to each result for complete understanding
|
|
4097
|
+
const sliced = results.slice(0, 5);
|
|
4098
|
+
for (const r of sliced) {
|
|
4099
|
+
if (r.path && r.path.endsWith('.php')) {
|
|
4100
|
+
const body = readFullMethodBody(r.path, args.methodName);
|
|
4101
|
+
if (body) r.fullMethodBody = body;
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
3995
4104
|
return {
|
|
3996
4105
|
content: [{
|
|
3997
4106
|
type: 'text',
|
|
3998
|
-
text: formatSearchResults(
|
|
4107
|
+
text: formatSearchResults(sliced)
|
|
3999
4108
|
}]
|
|
4000
4109
|
};
|
|
4001
4110
|
}
|
|
@@ -4093,18 +4202,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4093
4202
|
return { ...r, diArea };
|
|
4094
4203
|
});
|
|
4095
4204
|
|
|
4096
|
-
// If targetClass provided, also scan di.xml for explicit registrations
|
|
4205
|
+
// If targetClass provided, also scan di.xml for explicit registrations (using session cache)
|
|
4097
4206
|
let diRegistrations = [];
|
|
4098
4207
|
if (args.targetClass) {
|
|
4099
4208
|
const fpRoot = config.magentoRoot;
|
|
4100
|
-
const diFiles = await
|
|
4209
|
+
const diFiles = await getDiXmlFiles(fpRoot);
|
|
4101
4210
|
// Normalize target class for matching (both \ and \\)
|
|
4102
4211
|
const normalizedTarget = args.targetClass.replace(/\\\\/g, '\\');
|
|
4103
|
-
for (const
|
|
4104
|
-
let content;
|
|
4105
|
-
try { content = readFileSync(diFile, 'utf-8'); } catch { continue; }
|
|
4212
|
+
for (const { content, relPath } of diFiles) {
|
|
4106
4213
|
if (!content.includes(normalizedTarget)) continue;
|
|
4107
|
-
const relPath = diFile.replace(fpRoot + '/', '');
|
|
4108
4214
|
// Find plugin registrations for this target
|
|
4109
4215
|
const typeBlockRegex = /<type\s+name="([^"]+)"[^>]*>([\s\S]*?)<\/type>/g;
|
|
4110
4216
|
let tm;
|
|
@@ -4139,14 +4245,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4139
4245
|
}
|
|
4140
4246
|
}
|
|
4141
4247
|
|
|
4142
|
-
// Resolve plugin methods for DI registrations
|
|
4143
|
-
const
|
|
4248
|
+
// Resolve plugin methods + method bodies for DI registrations
|
|
4249
|
+
const fpRoot2 = args.targetClass ? config.magentoRoot : null;
|
|
4144
4250
|
for (const reg of diRegistrations) {
|
|
4145
|
-
if (reg.pluginClass &&
|
|
4146
|
-
const pluginFile = findClassFile(
|
|
4251
|
+
if (reg.pluginClass && fpRoot2) {
|
|
4252
|
+
const pluginFile = findClassFile(fpRoot2, reg.pluginClass);
|
|
4147
4253
|
if (pluginFile) {
|
|
4148
4254
|
reg.methods = extractPluginMethods(pluginFile);
|
|
4149
|
-
reg.resolvedFile = pluginFile.replace(
|
|
4255
|
+
reg.resolvedFile = pluginFile.replace(fpRoot2 + '/', '');
|
|
4256
|
+
// Read full method bodies so the agent sees actual code without follow-up calls
|
|
4257
|
+
for (const m of reg.methods) {
|
|
4258
|
+
const body = readFullMethodBody(pluginFile, m.name);
|
|
4259
|
+
if (body) m.body = body;
|
|
4260
|
+
}
|
|
4150
4261
|
}
|
|
4151
4262
|
}
|
|
4152
4263
|
}
|
|
@@ -4164,6 +4275,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4164
4275
|
if (reg.methods?.length > 0) {
|
|
4165
4276
|
for (const m of reg.methods) {
|
|
4166
4277
|
text += ` - \`${m.type}\` **${m.targetMethod}** → \`${m.name}()\`\n`;
|
|
4278
|
+
if (m.body) {
|
|
4279
|
+
const indentedBody = m.body.split('\n').join('\n ');
|
|
4280
|
+
text += ' ' + '```php\n ' + indentedBody + '\n ' + '```\n';
|
|
4281
|
+
}
|
|
4167
4282
|
}
|
|
4168
4283
|
}
|
|
4169
4284
|
}
|
|
@@ -5394,6 +5509,30 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
5394
5509
|
for (const o of flow.observers.slice(0, 10)) text += `- ${o.name}: ${o.instance} (${o.file})\n`;
|
|
5395
5510
|
break;
|
|
5396
5511
|
}
|
|
5512
|
+
case 'magento_find_di_wiring': {
|
|
5513
|
+
const wiring = await findDiWiring(a.className);
|
|
5514
|
+
text = `Prefs: ${wiring.preferences.length}, Plugins: ${wiring.plugins.length}, VTs: ${wiring.virtualTypes.length}, Ctor: ${wiring.constructorArguments.length}\n`;
|
|
5515
|
+
for (const p of wiring.plugins.slice(0, 5)) text += `- plugin: ${p.pluginName} → ${p.pluginClass}\n`;
|
|
5516
|
+
for (const c of wiring.constructorArguments.slice(0, 10)) text += `- ctor: ${c.typeHint || '?'} ${c.variable}\n`;
|
|
5517
|
+
if (wiring.constructorSourceFile) text += `Source: ${wiring.constructorSourceFile}\n`;
|
|
5518
|
+
break;
|
|
5519
|
+
}
|
|
5520
|
+
case 'magento_find_method': {
|
|
5521
|
+
const qr = `method ${a.methodName} function ${a.className || ''}`.trim();
|
|
5522
|
+
const raw = await rustSearchAsync(qr, 50);
|
|
5523
|
+
const ml = a.methodName.toLowerCase();
|
|
5524
|
+
let res = raw.map(normalizeResult).filter(r =>
|
|
5525
|
+
r.methodName?.toLowerCase() === ml || r.methods?.some(m => m.toLowerCase() === ml)
|
|
5526
|
+
);
|
|
5527
|
+
for (const r of res.slice(0, 5)) {
|
|
5528
|
+
if (r.path?.endsWith('.php')) {
|
|
5529
|
+
const body = readFullMethodBody(r.path, a.methodName);
|
|
5530
|
+
if (body) r.fullMethodBody = body;
|
|
5531
|
+
}
|
|
5532
|
+
}
|
|
5533
|
+
text = formatSearchResults(res.slice(0, 5));
|
|
5534
|
+
break;
|
|
5535
|
+
}
|
|
5397
5536
|
default:
|
|
5398
5537
|
text = `Unsupported batch tool: ${q.tool}`;
|
|
5399
5538
|
}
|