magector 2.6.1 → 2.6.3
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/cli.js +8 -0
- package/src/mcp-server.js +127 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magector",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.3",
|
|
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.6.
|
|
37
|
-
"@magector/cli-linux-x64": "2.6.
|
|
38
|
-
"@magector/cli-linux-arm64": "2.6.
|
|
39
|
-
"@magector/cli-win32-x64": "2.6.
|
|
36
|
+
"@magector/cli-darwin-arm64": "2.6.3",
|
|
37
|
+
"@magector/cli-linux-x64": "2.6.3",
|
|
38
|
+
"@magector/cli-linux-arm64": "2.6.3",
|
|
39
|
+
"@magector/cli-win32-x64": "2.6.3"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
42
42
|
"magento",
|
package/src/cli.js
CHANGED
|
@@ -12,6 +12,8 @@ import { resolveBinary } from './binary.js';
|
|
|
12
12
|
import { ensureModels, resolveModels } from './model.js';
|
|
13
13
|
import { init, setup } from './init.js';
|
|
14
14
|
import { checkForUpdate } from './update.js';
|
|
15
|
+
import { createRequire } from 'module';
|
|
16
|
+
const __cliPkg = createRequire(import.meta.url)('../package.json');
|
|
15
17
|
|
|
16
18
|
const args = process.argv.slice(2);
|
|
17
19
|
const command = args[0];
|
|
@@ -327,6 +329,12 @@ async function main() {
|
|
|
327
329
|
await import('./validation/benchmark.js');
|
|
328
330
|
break;
|
|
329
331
|
|
|
332
|
+
case 'version':
|
|
333
|
+
case '--version':
|
|
334
|
+
case '-V':
|
|
335
|
+
console.log(`magector v${__cliPkg.version}`);
|
|
336
|
+
break;
|
|
337
|
+
|
|
330
338
|
case 'help':
|
|
331
339
|
case '--help':
|
|
332
340
|
case '-h':
|
package/src/mcp-server.js
CHANGED
|
@@ -2279,7 +2279,20 @@ async function analyzeImpact(className) {
|
|
|
2279
2279
|
// Use vector search to find candidate files (much faster than globbing all PHP)
|
|
2280
2280
|
const rawSearch = await rustSearchAsync(`${shortName} ${className}`, 50).catch(() => []);
|
|
2281
2281
|
const raw = Array.isArray(rawSearch) ? rawSearch : [];
|
|
2282
|
-
|
|
2282
|
+
let relatedPaths = raw.map(r => normalizeResult(r)).filter(r => r.path);
|
|
2283
|
+
|
|
2284
|
+
// Filesystem fallback: if vector search found too few files, find the class file via glob
|
|
2285
|
+
if (relatedPaths.length < 5 && root) {
|
|
2286
|
+
try {
|
|
2287
|
+
const classFiles = await glob(`**/${shortName}.php`, { cwd: root, absolute: false, nodir: true });
|
|
2288
|
+
const existingPaths = new Set(relatedPaths.map(r => r.path));
|
|
2289
|
+
for (const f of classFiles) {
|
|
2290
|
+
if (!existingPaths.has(f)) {
|
|
2291
|
+
relatedPaths.push({ path: f, className: shortName, score: 0.3 });
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
} catch {}
|
|
2295
|
+
}
|
|
2283
2296
|
|
|
2284
2297
|
// Check DI references via xml parsing
|
|
2285
2298
|
const diTrace = await traceDependency(className, 'both');
|
|
@@ -4075,10 +4088,49 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4075
4088
|
const query = `${args.className} ${ns}`.trim();
|
|
4076
4089
|
const raw = await rustSearchAsync(query, 30);
|
|
4077
4090
|
const classLower = args.className.toLowerCase();
|
|
4078
|
-
|
|
4091
|
+
let results = raw.map(normalizeResult).filter(r =>
|
|
4079
4092
|
r.className?.toLowerCase().includes(classLower) ||
|
|
4080
4093
|
r.path?.toLowerCase().includes(classLower.replace(/\\/g, '/'))
|
|
4081
4094
|
);
|
|
4095
|
+
|
|
4096
|
+
// Filesystem fallback: if vector search found nothing, glob for ClassName.php
|
|
4097
|
+
if (results.length === 0 && config.magentoRoot) {
|
|
4098
|
+
const shortName = args.className.split('\\').pop();
|
|
4099
|
+
const globPattern = `**/${shortName}.php`;
|
|
4100
|
+
try {
|
|
4101
|
+
const files = await glob(globPattern, { cwd: config.magentoRoot, absolute: false, nodir: true, ignore: ['**/test/**', '**/tests/**', '**/Test/**'] });
|
|
4102
|
+
// Filter by namespace if provided
|
|
4103
|
+
const nsLower = ns.toLowerCase().replace(/\\\\/g, '/').replace(/\\/g, '/');
|
|
4104
|
+
const matched = files.filter(f => {
|
|
4105
|
+
if (!nsLower) return true;
|
|
4106
|
+
return f.toLowerCase().includes(nsLower);
|
|
4107
|
+
}).slice(0, 10);
|
|
4108
|
+
// Build result entries from file paths
|
|
4109
|
+
for (const filePath of matched) {
|
|
4110
|
+
const absPath = path.join(config.magentoRoot, filePath);
|
|
4111
|
+
let className = shortName;
|
|
4112
|
+
try {
|
|
4113
|
+
const content = readFileSync(absPath, 'utf-8');
|
|
4114
|
+
const nsMatch = content.match(/namespace\s+([\w\\]+)/);
|
|
4115
|
+
if (nsMatch) className = nsMatch[1] + '\\' + shortName;
|
|
4116
|
+
const methodsFound = [];
|
|
4117
|
+
const methodRegex = /public\s+function\s+(\w+)\s*\(/g;
|
|
4118
|
+
let mm;
|
|
4119
|
+
while ((mm = methodRegex.exec(content)) !== null) methodsFound.push(mm[1]);
|
|
4120
|
+
results.push({
|
|
4121
|
+
path: filePath,
|
|
4122
|
+
className,
|
|
4123
|
+
methods: methodsFound,
|
|
4124
|
+
score: 0.5,
|
|
4125
|
+
searchText: content.slice(0, 300)
|
|
4126
|
+
});
|
|
4127
|
+
} catch {
|
|
4128
|
+
results.push({ path: filePath, className, score: 0.5 });
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4131
|
+
} catch {}
|
|
4132
|
+
}
|
|
4133
|
+
|
|
4082
4134
|
return {
|
|
4083
4135
|
content: [{
|
|
4084
4136
|
type: 'text',
|
|
@@ -4104,6 +4156,41 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4104
4156
|
);
|
|
4105
4157
|
results = results.concat(pathFallback);
|
|
4106
4158
|
}
|
|
4159
|
+
// Filesystem fallback: grep for the method definition in PHP files
|
|
4160
|
+
if (results.length === 0 && config.magentoRoot) {
|
|
4161
|
+
try {
|
|
4162
|
+
const methodSig = `function ${args.methodName}(`;
|
|
4163
|
+
// Use className to narrow the glob if provided
|
|
4164
|
+
const classShort = args.className ? args.className.split('\\').pop() : null;
|
|
4165
|
+
const globPattern = classShort ? `**/${classShort}.php` : '**/*.php';
|
|
4166
|
+
const files = await glob(globPattern, { cwd: config.magentoRoot, absolute: false, nodir: true, ignore: ['**/test/**', '**/tests/**', '**/Test/**'] });
|
|
4167
|
+
for (const f of files.slice(0, classShort ? 50 : 500)) {
|
|
4168
|
+
const absPath = path.join(config.magentoRoot, f);
|
|
4169
|
+
let content;
|
|
4170
|
+
try { content = readFileSync(absPath, 'utf-8'); } catch { continue; }
|
|
4171
|
+
if (!content.includes(methodSig)) continue;
|
|
4172
|
+
let className = null;
|
|
4173
|
+
const nsMatch = content.match(/namespace\s+([\w\\]+)/);
|
|
4174
|
+
const classMatch = content.match(/class\s+(\w+)/);
|
|
4175
|
+
if (nsMatch && classMatch) className = nsMatch[1] + '\\' + classMatch[1];
|
|
4176
|
+
const methodsFound = [];
|
|
4177
|
+
const mRegex = /public\s+function\s+(\w+)\s*\(/g;
|
|
4178
|
+
let mm;
|
|
4179
|
+
while ((mm = mRegex.exec(content)) !== null) methodsFound.push(mm[1]);
|
|
4180
|
+
const body = readFullMethodBody(absPath, args.methodName);
|
|
4181
|
+
results.push({
|
|
4182
|
+
path: f,
|
|
4183
|
+
className,
|
|
4184
|
+
methods: methodsFound,
|
|
4185
|
+
methodName: args.methodName,
|
|
4186
|
+
score: 0.5,
|
|
4187
|
+
searchText: content.slice(0, 300),
|
|
4188
|
+
fullMethodBody: body || undefined
|
|
4189
|
+
});
|
|
4190
|
+
if (results.length >= 5) break;
|
|
4191
|
+
}
|
|
4192
|
+
} catch {}
|
|
4193
|
+
}
|
|
4107
4194
|
// Boost exact method matches to top
|
|
4108
4195
|
results = results.map(r => {
|
|
4109
4196
|
let bonus = 0;
|
|
@@ -4632,18 +4719,40 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4632
4719
|
// Support both app/code (Magento/Catalog/) and vendor (module-catalog/) paths
|
|
4633
4720
|
const modulePath = args.moduleName.replace('_', '/') + '/';
|
|
4634
4721
|
const parts = args.moduleName.split('_');
|
|
4722
|
+
// Hyphenate camelCase for vendor path: OrderSplit → order-split
|
|
4635
4723
|
const vendorPath = parts.length === 2
|
|
4636
|
-
? `module-${parts[1].toLowerCase()}/`
|
|
4724
|
+
? `module-${parts[1].replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}/`
|
|
4637
4725
|
: '';
|
|
4638
|
-
|
|
4639
|
-
const
|
|
4726
|
+
let results = raw.map(normalizeResult).filter(r => {
|
|
4727
|
+
const p = r.path || '';
|
|
4640
4728
|
const mod = r.module || '';
|
|
4641
4729
|
// Exact module match or directory-level path match (trailing slash prevents Catalog matching CatalogRule)
|
|
4642
4730
|
return mod === args.moduleName ||
|
|
4643
|
-
|
|
4644
|
-
(vendorPath &&
|
|
4731
|
+
p.includes(modulePath) ||
|
|
4732
|
+
(vendorPath && p.toLowerCase().includes(vendorPath));
|
|
4645
4733
|
});
|
|
4646
4734
|
|
|
4735
|
+
// Filesystem fallback: if vector search found nothing, glob the module directory
|
|
4736
|
+
if (results.length === 0 && config.magentoRoot && vendorPath) {
|
|
4737
|
+
try {
|
|
4738
|
+
const vendorGlob = `**/${vendorPath}**/*.{php,xml,phtml}`;
|
|
4739
|
+
const files = await glob(vendorGlob, { cwd: config.magentoRoot, absolute: false, nodir: true });
|
|
4740
|
+
for (const f of files.slice(0, 100)) {
|
|
4741
|
+
const entry = { path: f, score: 0.5 };
|
|
4742
|
+
if (f.includes('/Controller/')) entry.isController = true;
|
|
4743
|
+
if (f.includes('/Model/')) entry.isModel = true;
|
|
4744
|
+
if (f.includes('/Block/')) entry.isBlock = true;
|
|
4745
|
+
if (f.includes('/Plugin/')) entry.isPlugin = true;
|
|
4746
|
+
if (f.includes('/Observer/')) entry.isObserver = true;
|
|
4747
|
+
if (f.endsWith('.xml')) entry.type = 'xml';
|
|
4748
|
+
// Extract class name from path
|
|
4749
|
+
const phpMatch = f.match(/\/([A-Z]\w+)\.php$/);
|
|
4750
|
+
if (phpMatch) entry.className = phpMatch[1];
|
|
4751
|
+
results.push(entry);
|
|
4752
|
+
}
|
|
4753
|
+
} catch {}
|
|
4754
|
+
}
|
|
4755
|
+
|
|
4647
4756
|
const structure = {
|
|
4648
4757
|
controllers: results.filter(r => r.isController || r.path?.includes('/Controller/')),
|
|
4649
4758
|
models: results.filter(r => r.isModel || (r.path?.includes('/Model/') && !r.path?.includes('ResourceModel'))),
|
|
@@ -5490,9 +5599,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
5490
5599
|
const qr = `${a.className} ${ns}`.trim();
|
|
5491
5600
|
const raw = await rustSearchAsync(qr, 30);
|
|
5492
5601
|
const cl = a.className.toLowerCase();
|
|
5493
|
-
|
|
5602
|
+
let res = raw.map(normalizeResult).filter(r =>
|
|
5494
5603
|
r.className?.toLowerCase().includes(cl) || r.path?.toLowerCase().includes(cl.replace(/\\/g, '/'))
|
|
5495
5604
|
);
|
|
5605
|
+
// Filesystem fallback for batch find_class
|
|
5606
|
+
if (res.length === 0 && config.magentoRoot) {
|
|
5607
|
+
const shortName = a.className.split('\\').pop();
|
|
5608
|
+
try {
|
|
5609
|
+
const files = await glob(`**/${shortName}.php`, { cwd: config.magentoRoot, absolute: false, nodir: true });
|
|
5610
|
+
for (const f of files.slice(0, 5)) {
|
|
5611
|
+
res.push({ path: f, className: shortName, score: 0.5 });
|
|
5612
|
+
}
|
|
5613
|
+
} catch {}
|
|
5614
|
+
}
|
|
5496
5615
|
text = formatSearchResults(res.slice(0, 5));
|
|
5497
5616
|
break;
|
|
5498
5617
|
}
|