magector 2.7.0 → 2.7.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 +99 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magector",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.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.7.
|
|
37
|
-
"@magector/cli-linux-x64": "2.7.
|
|
38
|
-
"@magector/cli-linux-arm64": "2.7.
|
|
39
|
-
"@magector/cli-win32-x64": "2.7.
|
|
36
|
+
"@magector/cli-darwin-arm64": "2.7.2",
|
|
37
|
+
"@magector/cli-linux-x64": "2.7.2",
|
|
38
|
+
"@magector/cli-linux-arm64": "2.7.2",
|
|
39
|
+
"@magector/cli-win32-x64": "2.7.2"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
42
42
|
"magento",
|
package/src/mcp-server.js
CHANGED
|
@@ -2303,6 +2303,33 @@ async function analyzeImpact(className) {
|
|
|
2303
2303
|
...diTrace.virtualTypes.map(v => ({ type: 'virtualType', file: v.file, detail: `${v.name} extends ${v.type}` })),
|
|
2304
2304
|
...diTrace.argumentOverrides.map(a => ({ type: 'argument', file: a.file, detail: `${a.target}.${a.argumentName}` }))
|
|
2305
2305
|
];
|
|
2306
|
+
// Also find where this class is used AS a plugin/preference/virtualType implementation
|
|
2307
|
+
if (references.diXmlReferences.length === 0 && root) {
|
|
2308
|
+
const diFiles = await getDiXmlFiles(root);
|
|
2309
|
+
for (const { content, relPath } of diFiles) {
|
|
2310
|
+
if (!content.toLowerCase().includes(shortName.toLowerCase())) continue;
|
|
2311
|
+
// Check if class appears as plugin type
|
|
2312
|
+
const pluginTypeRegex = /<plugin\s+[^>]*type="([^"]+)"[^>]*\/?>/g;
|
|
2313
|
+
let pm;
|
|
2314
|
+
while ((pm = pluginTypeRegex.exec(content)) !== null) {
|
|
2315
|
+
if (pm[1].toLowerCase().includes(shortName.toLowerCase())) {
|
|
2316
|
+
// Find parent <type> to know the target
|
|
2317
|
+
const beforePlugin = content.slice(0, pm.index);
|
|
2318
|
+
const typeMatch = beforePlugin.match(/<type\s+name="([^"]+)"[^>]*>\s*$/m);
|
|
2319
|
+
const target = typeMatch ? typeMatch[1] : 'unknown';
|
|
2320
|
+
references.diXmlReferences.push({ type: 'registered-as-plugin', file: relPath, detail: `plugin on ${target} → ${pm[1]}` });
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
// Check if class appears as preference type
|
|
2324
|
+
const prefRegex = /<preference\s+for="([^"]+)"\s+type="([^"]+)"\s*\/?>/g;
|
|
2325
|
+
let prm;
|
|
2326
|
+
while ((prm = prefRegex.exec(content)) !== null) {
|
|
2327
|
+
if (prm[2].toLowerCase().includes(shortName.toLowerCase())) {
|
|
2328
|
+
references.diXmlReferences.push({ type: 'registered-as-preference', file: relPath, detail: `preference for ${prm[1]} → ${prm[2]}` });
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2306
2333
|
|
|
2307
2334
|
// Check PHP files for direct references
|
|
2308
2335
|
const escapedShort = shortName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
@@ -5640,18 +5667,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
5640
5667
|
const res = raw.map(normalizeResult).filter(r =>
|
|
5641
5668
|
r.magentoType === 'plugin' || r.path?.toLowerCase().includes('plugin')
|
|
5642
5669
|
);
|
|
5643
|
-
text = formatSearchResults(res.slice(0,
|
|
5644
|
-
// DI registrations + method bodies (
|
|
5670
|
+
text = formatSearchResults(res.slice(0, 3));
|
|
5671
|
+
// DI registrations + method bodies (compact: only targetMethod bodies)
|
|
5645
5672
|
if (a.targetClass) {
|
|
5646
5673
|
const diFiles = await getDiXmlFiles(config.magentoRoot);
|
|
5647
5674
|
const normalizedTarget = a.targetClass.replace(/\\\\/g, '\\');
|
|
5648
5675
|
const isFqcn = normalizedTarget.includes('\\');
|
|
5649
5676
|
const shortTarget = normalizedTarget.split('\\').pop().toLowerCase();
|
|
5677
|
+
let regCount = 0;
|
|
5678
|
+
text += '\n\n### DI Registrations\n';
|
|
5650
5679
|
for (const { content: diContent, relPath } of diFiles) {
|
|
5680
|
+
if (regCount >= 8) break;
|
|
5651
5681
|
if (!diContent.includes(isFqcn ? normalizedTarget : a.targetClass)) continue;
|
|
5652
5682
|
const typeBlockRegex = /<type\s+name="([^"]+)"[^>]*>([\s\S]*?)<\/type>/g;
|
|
5653
5683
|
let tm;
|
|
5654
5684
|
while ((tm = typeBlockRegex.exec(diContent)) !== null) {
|
|
5685
|
+
if (regCount >= 8) break;
|
|
5655
5686
|
const typeName = tm[1].replace(/\\\\/g, '\\');
|
|
5656
5687
|
const typeMatches = isFqcn ? typeName === normalizedTarget : typeName.split('\\').pop().toLowerCase() === shortTarget;
|
|
5657
5688
|
if (!typeMatches) continue;
|
|
@@ -5659,22 +5690,27 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
5659
5690
|
const pluginRegex = /<plugin\s+([^/>]*)\/?>/g;
|
|
5660
5691
|
let pm;
|
|
5661
5692
|
while ((pm = pluginRegex.exec(block)) !== null) {
|
|
5693
|
+
if (regCount >= 8) break;
|
|
5662
5694
|
const attrs = {};
|
|
5663
5695
|
const localAttrRe = /(\w+)="([^"]*)"/g;
|
|
5664
|
-
let
|
|
5665
|
-
while ((
|
|
5666
|
-
|
|
5667
|
-
|
|
5696
|
+
let am2;
|
|
5697
|
+
while ((am2 = localAttrRe.exec(pm[1])) !== null) attrs[am2[1]] = am2[2];
|
|
5698
|
+
const disabled = attrs.disabled === 'true' ? ' [DISABLED]' : '';
|
|
5699
|
+
text += `- **${attrs.name || '?'}** → \`${attrs.type || '?'}\`${disabled} (${relPath})\n`;
|
|
5700
|
+
// Only read method bodies for plugins that intercept the targetMethod
|
|
5701
|
+
if (attrs.type && a.targetMethod) {
|
|
5668
5702
|
const pFile = findClassFile(config.magentoRoot, attrs.type);
|
|
5669
5703
|
if (pFile) {
|
|
5670
5704
|
const methods = extractPluginMethods(pFile);
|
|
5671
|
-
|
|
5705
|
+
const relevant = methods.filter(m => m.targetMethod === a.targetMethod);
|
|
5706
|
+
for (const m of relevant) {
|
|
5672
5707
|
const body = readFullMethodBody(pFile, m.name);
|
|
5673
|
-
text +=
|
|
5674
|
-
if (body) text += '
|
|
5708
|
+
text += ` - \`${m.type}\` **${m.targetMethod}** → \`${m.name}()\`\n`;
|
|
5709
|
+
if (body) text += ' ' + '```php\n ' + body.split('\n').join('\n ') + '\n ' + '```\n';
|
|
5675
5710
|
}
|
|
5676
5711
|
}
|
|
5677
5712
|
}
|
|
5713
|
+
regCount++;
|
|
5678
5714
|
}
|
|
5679
5715
|
}
|
|
5680
5716
|
}
|
|
@@ -5740,9 +5776,37 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
5740
5776
|
let res = raw.map(normalizeResult).filter(r =>
|
|
5741
5777
|
r.methodName?.toLowerCase() === ml || r.methods?.some(m => m.toLowerCase() === ml)
|
|
5742
5778
|
);
|
|
5779
|
+
// Filesystem fallback: grep -rl for method signature
|
|
5780
|
+
if (res.length === 0 && config.magentoRoot) {
|
|
5781
|
+
const methodSig = `function ${a.methodName}(`;
|
|
5782
|
+
const classShort = a.className ? a.className.split('\\').pop() : null;
|
|
5783
|
+
try {
|
|
5784
|
+
let files = [];
|
|
5785
|
+
if (classShort) {
|
|
5786
|
+
files = await glob(`**/${classShort}.php`, { cwd: config.magentoRoot, absolute: false, nodir: true });
|
|
5787
|
+
} else {
|
|
5788
|
+
const grepResult = execFileSync('grep', ['-rl', '--include=*.php', methodSig, '.'],
|
|
5789
|
+
{ cwd: config.magentoRoot, encoding: 'utf-8', timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
5790
|
+
files = grepResult.trim().split('\n').filter(Boolean).map(f => f.replace(/^\.\//, ''));
|
|
5791
|
+
}
|
|
5792
|
+
for (const f of files.slice(0, 10)) {
|
|
5793
|
+
const absP = f.startsWith('/') ? f : path.join(config.magentoRoot, f);
|
|
5794
|
+
let content;
|
|
5795
|
+
try { content = readFileSync(absP, 'utf-8'); } catch { continue; }
|
|
5796
|
+
if (!content.includes(methodSig)) continue;
|
|
5797
|
+
let cn = null;
|
|
5798
|
+
const nsM = content.match(/namespace\s+([\w\\]+)/);
|
|
5799
|
+
const clM = content.match(/class\s+(\w+)/);
|
|
5800
|
+
if (nsM && clM) cn = nsM[1] + '\\' + clM[1];
|
|
5801
|
+
const body = readFullMethodBody(absP, a.methodName);
|
|
5802
|
+
res.push({ path: f, className: cn, methodName: a.methodName, score: 0.5, fullMethodBody: body || undefined });
|
|
5803
|
+
if (res.length >= 5) break;
|
|
5804
|
+
}
|
|
5805
|
+
} catch {}
|
|
5806
|
+
}
|
|
5743
5807
|
for (const r of res.slice(0, 5)) {
|
|
5744
|
-
if (r.path?.endsWith('.php')) {
|
|
5745
|
-
const body = readFullMethodBody(r.path, a.methodName);
|
|
5808
|
+
if (r.path?.endsWith('.php') && !r.fullMethodBody) {
|
|
5809
|
+
const body = readFullMethodBody(path.join(config.magentoRoot, r.path), a.methodName);
|
|
5746
5810
|
if (body) r.fullMethodBody = body;
|
|
5747
5811
|
}
|
|
5748
5812
|
}
|
|
@@ -5752,13 +5816,34 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
5752
5816
|
case 'magento_module_structure': {
|
|
5753
5817
|
const raw = await rustSearchAsync(a.moduleName, 200);
|
|
5754
5818
|
const modulePath = a.moduleName.replace('_', '/') + '/';
|
|
5755
|
-
const
|
|
5756
|
-
|
|
5757
|
-
const
|
|
5819
|
+
const mParts = a.moduleName.split('_');
|
|
5820
|
+
// Hyphenate camelCase for vendor path: OrderSplit → order-split
|
|
5821
|
+
const vendorPath = mParts.length === 2
|
|
5822
|
+
? `module-${mParts[1].replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}/`
|
|
5823
|
+
: '';
|
|
5824
|
+
let res = raw.map(normalizeResult).filter(r => {
|
|
5758
5825
|
const p = r.path || '';
|
|
5759
5826
|
const mod = r.module || '';
|
|
5760
5827
|
return mod === a.moduleName || p.includes(modulePath) || (vendorPath && p.toLowerCase().includes(vendorPath));
|
|
5761
5828
|
});
|
|
5829
|
+
// Filesystem fallback
|
|
5830
|
+
if (res.length === 0 && config.magentoRoot && vendorPath) {
|
|
5831
|
+
try {
|
|
5832
|
+
const vendorGlob = `**/${vendorPath}**/*.{php,xml,phtml}`;
|
|
5833
|
+
const files = await glob(vendorGlob, { cwd: config.magentoRoot, absolute: false, nodir: true });
|
|
5834
|
+
for (const f of files.slice(0, 100)) {
|
|
5835
|
+
const entry = { path: f, score: 0.5 };
|
|
5836
|
+
if (f.includes('/Controller/')) entry.isController = true;
|
|
5837
|
+
if (f.includes('/Model/')) entry.isModel = true;
|
|
5838
|
+
if (f.includes('/Plugin/')) entry.isPlugin = true;
|
|
5839
|
+
if (f.includes('/Observer/')) entry.isObserver = true;
|
|
5840
|
+
if (f.endsWith('.xml')) entry.type = 'xml';
|
|
5841
|
+
const phpMatch = f.match(/\/([A-Z]\w+)\.php$/);
|
|
5842
|
+
if (phpMatch) entry.className = phpMatch[1];
|
|
5843
|
+
res.push(entry);
|
|
5844
|
+
}
|
|
5845
|
+
} catch {}
|
|
5846
|
+
}
|
|
5762
5847
|
text = `Module: ${a.moduleName} (${res.length} files)\n`;
|
|
5763
5848
|
const cats = { controllers: '/Controller/', models: '/Model/', plugins: '/Plugin/', observers: '/Observer/', api: '/Api/' };
|
|
5764
5849
|
for (const [cat, pattern] of Object.entries(cats)) {
|