magector 2.5.2 → 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 +71 -29
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
|
@@ -1596,8 +1596,12 @@ function formatSearchResults(results) {
|
|
|
1596
1596
|
if (r.isBlock) badges.push('block');
|
|
1597
1597
|
if (badges.length > 0) entry.badges = badges;
|
|
1598
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
|
+
|
|
1599
1603
|
// Snippet — first 300 chars of indexed content for quick assessment
|
|
1600
|
-
if (r.searchText) {
|
|
1604
|
+
if (isTopRanked && r.searchText) {
|
|
1601
1605
|
entry.snippet = r.searchText.length > 300
|
|
1602
1606
|
? r.searchText.slice(0, 300) + '...'
|
|
1603
1607
|
: r.searchText;
|
|
@@ -1608,7 +1612,7 @@ function formatSearchResults(results) {
|
|
|
1608
1612
|
entry.codePreview = r.fullMethodBody;
|
|
1609
1613
|
}
|
|
1610
1614
|
// Code preview — read actual source lines for PHP files with known class/method
|
|
1611
|
-
else 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'))) {
|
|
1612
1616
|
if (r.methodName) {
|
|
1613
1617
|
const preview = readMethodSnippet(r.path, r.methodName, 10);
|
|
1614
1618
|
if (preview) entry.codePreview = preview;
|
|
@@ -1639,6 +1643,47 @@ function formatSearchResults(results) {
|
|
|
1639
1643
|
return JSON.stringify({ results: formatted, count: formatted.length });
|
|
1640
1644
|
}
|
|
1641
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
|
+
|
|
1642
1687
|
// ─── DI Dependency Tracing ─────────────────────────────────────
|
|
1643
1688
|
|
|
1644
1689
|
/**
|
|
@@ -1647,7 +1692,7 @@ function formatSearchResults(results) {
|
|
|
1647
1692
|
*/
|
|
1648
1693
|
async function traceDependency(className, direction = 'both') {
|
|
1649
1694
|
const root = config.magentoRoot;
|
|
1650
|
-
const diFiles = await
|
|
1695
|
+
const diFiles = await getDiXmlFiles(root);
|
|
1651
1696
|
const classLower = className.toLowerCase();
|
|
1652
1697
|
const classShort = className.split('\\').pop().toLowerCase();
|
|
1653
1698
|
|
|
@@ -1660,13 +1705,7 @@ async function traceDependency(className, direction = 'both') {
|
|
|
1660
1705
|
totalDiFiles: diFiles.length
|
|
1661
1706
|
};
|
|
1662
1707
|
|
|
1663
|
-
for (const
|
|
1664
|
-
let content;
|
|
1665
|
-
try {
|
|
1666
|
-
content = readFileSync(diFile, 'utf-8');
|
|
1667
|
-
} catch { continue; }
|
|
1668
|
-
|
|
1669
|
-
const relativePath = diFile.replace(root + '/', '');
|
|
1708
|
+
for (const { content, relPath: relativePath } of diFiles) {
|
|
1670
1709
|
|
|
1671
1710
|
if (direction === 'resolve' || direction === 'both') {
|
|
1672
1711
|
// Find preferences: <preference for="ClassName" type="Implementation"/>
|
|
@@ -2618,13 +2657,10 @@ async function findDiWiring(className) {
|
|
|
2618
2657
|
totalDiFiles: 0
|
|
2619
2658
|
};
|
|
2620
2659
|
|
|
2621
|
-
const diFiles = await
|
|
2660
|
+
const diFiles = await getDiXmlFiles(root);
|
|
2622
2661
|
result.totalDiFiles = diFiles.length;
|
|
2623
2662
|
|
|
2624
|
-
for (const
|
|
2625
|
-
let content;
|
|
2626
|
-
try { content = readFileSync(diFile, 'utf-8'); } catch { continue; }
|
|
2627
|
-
const relativePath = diFile.replace(root + '/', '');
|
|
2663
|
+
for (const { content, relPath: relativePath } of diFiles) {
|
|
2628
2664
|
const contentLower = content.toLowerCase();
|
|
2629
2665
|
|
|
2630
2666
|
// Quick pre-filter: skip files that don't contain the short name at all
|
|
@@ -4011,7 +4047,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4011
4047
|
return {
|
|
4012
4048
|
content: [{
|
|
4013
4049
|
type: 'text',
|
|
4014
|
-
text: formatSearchResults(results.slice(0, args.limit ||
|
|
4050
|
+
text: formatSearchResults(results.slice(0, args.limit || 5))
|
|
4015
4051
|
}]
|
|
4016
4052
|
};
|
|
4017
4053
|
}
|
|
@@ -4028,7 +4064,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4028
4064
|
return {
|
|
4029
4065
|
content: [{
|
|
4030
4066
|
type: 'text',
|
|
4031
|
-
text: formatSearchResults(results.slice(0,
|
|
4067
|
+
text: formatSearchResults(results.slice(0, 3))
|
|
4032
4068
|
}]
|
|
4033
4069
|
};
|
|
4034
4070
|
}
|
|
@@ -4058,7 +4094,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4058
4094
|
return { ...r, score: (r.score || 0) + bonus };
|
|
4059
4095
|
}).sort((a, b) => b.score - a.score);
|
|
4060
4096
|
// Attach full method body to each result for complete understanding
|
|
4061
|
-
const sliced = results.slice(0,
|
|
4097
|
+
const sliced = results.slice(0, 5);
|
|
4062
4098
|
for (const r of sliced) {
|
|
4063
4099
|
if (r.path && r.path.endsWith('.php')) {
|
|
4064
4100
|
const body = readFullMethodBody(r.path, args.methodName);
|
|
@@ -4166,18 +4202,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4166
4202
|
return { ...r, diArea };
|
|
4167
4203
|
});
|
|
4168
4204
|
|
|
4169
|
-
// If targetClass provided, also scan di.xml for explicit registrations
|
|
4205
|
+
// If targetClass provided, also scan di.xml for explicit registrations (using session cache)
|
|
4170
4206
|
let diRegistrations = [];
|
|
4171
4207
|
if (args.targetClass) {
|
|
4172
4208
|
const fpRoot = config.magentoRoot;
|
|
4173
|
-
const diFiles = await
|
|
4209
|
+
const diFiles = await getDiXmlFiles(fpRoot);
|
|
4174
4210
|
// Normalize target class for matching (both \ and \\)
|
|
4175
4211
|
const normalizedTarget = args.targetClass.replace(/\\\\/g, '\\');
|
|
4176
|
-
for (const
|
|
4177
|
-
let content;
|
|
4178
|
-
try { content = readFileSync(diFile, 'utf-8'); } catch { continue; }
|
|
4212
|
+
for (const { content, relPath } of diFiles) {
|
|
4179
4213
|
if (!content.includes(normalizedTarget)) continue;
|
|
4180
|
-
const relPath = diFile.replace(fpRoot + '/', '');
|
|
4181
4214
|
// Find plugin registrations for this target
|
|
4182
4215
|
const typeBlockRegex = /<type\s+name="([^"]+)"[^>]*>([\s\S]*?)<\/type>/g;
|
|
4183
4216
|
let tm;
|
|
@@ -4212,14 +4245,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4212
4245
|
}
|
|
4213
4246
|
}
|
|
4214
4247
|
|
|
4215
|
-
// Resolve plugin methods for DI registrations
|
|
4216
|
-
const
|
|
4248
|
+
// Resolve plugin methods + method bodies for DI registrations
|
|
4249
|
+
const fpRoot2 = args.targetClass ? config.magentoRoot : null;
|
|
4217
4250
|
for (const reg of diRegistrations) {
|
|
4218
|
-
if (reg.pluginClass &&
|
|
4219
|
-
const pluginFile = findClassFile(
|
|
4251
|
+
if (reg.pluginClass && fpRoot2) {
|
|
4252
|
+
const pluginFile = findClassFile(fpRoot2, reg.pluginClass);
|
|
4220
4253
|
if (pluginFile) {
|
|
4221
4254
|
reg.methods = extractPluginMethods(pluginFile);
|
|
4222
|
-
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
|
+
}
|
|
4223
4261
|
}
|
|
4224
4262
|
}
|
|
4225
4263
|
}
|
|
@@ -4237,6 +4275,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4237
4275
|
if (reg.methods?.length > 0) {
|
|
4238
4276
|
for (const m of reg.methods) {
|
|
4239
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
|
+
}
|
|
4240
4282
|
}
|
|
4241
4283
|
}
|
|
4242
4284
|
}
|