magector 2.6.1 → 2.6.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magector",
3
- "version": "2.6.1",
3
+ "version": "2.6.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.6.1",
37
- "@magector/cli-linux-x64": "2.6.1",
38
- "@magector/cli-linux-arm64": "2.6.1",
39
- "@magector/cli-win32-x64": "2.6.1"
36
+ "@magector/cli-darwin-arm64": "2.6.2",
37
+ "@magector/cli-linux-x64": "2.6.2",
38
+ "@magector/cli-linux-arm64": "2.6.2",
39
+ "@magector/cli-win32-x64": "2.6.2"
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
- const relatedPaths = raw.map(r => normalizeResult(r)).filter(r => r.path);
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
- const results = raw.map(normalizeResult).filter(r =>
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',
@@ -4632,18 +4684,40 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4632
4684
  // Support both app/code (Magento/Catalog/) and vendor (module-catalog/) paths
4633
4685
  const modulePath = args.moduleName.replace('_', '/') + '/';
4634
4686
  const parts = args.moduleName.split('_');
4687
+ // Hyphenate camelCase for vendor path: OrderSplit → order-split
4635
4688
  const vendorPath = parts.length === 2
4636
- ? `module-${parts[1].toLowerCase()}/`
4689
+ ? `module-${parts[1].replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}/`
4637
4690
  : '';
4638
- const results = raw.map(normalizeResult).filter(r => {
4639
- const path = r.path || '';
4691
+ let results = raw.map(normalizeResult).filter(r => {
4692
+ const p = r.path || '';
4640
4693
  const mod = r.module || '';
4641
4694
  // Exact module match or directory-level path match (trailing slash prevents Catalog matching CatalogRule)
4642
4695
  return mod === args.moduleName ||
4643
- path.includes(modulePath) ||
4644
- (vendorPath && path.toLowerCase().includes(vendorPath));
4696
+ p.includes(modulePath) ||
4697
+ (vendorPath && p.toLowerCase().includes(vendorPath));
4645
4698
  });
4646
4699
 
4700
+ // Filesystem fallback: if vector search found nothing, glob the module directory
4701
+ if (results.length === 0 && config.magentoRoot && vendorPath) {
4702
+ try {
4703
+ const vendorGlob = `**/${vendorPath}**/*.{php,xml,phtml}`;
4704
+ const files = await glob(vendorGlob, { cwd: config.magentoRoot, absolute: false, nodir: true });
4705
+ for (const f of files.slice(0, 100)) {
4706
+ const entry = { path: f, score: 0.5 };
4707
+ if (f.includes('/Controller/')) entry.isController = true;
4708
+ if (f.includes('/Model/')) entry.isModel = true;
4709
+ if (f.includes('/Block/')) entry.isBlock = true;
4710
+ if (f.includes('/Plugin/')) entry.isPlugin = true;
4711
+ if (f.includes('/Observer/')) entry.isObserver = true;
4712
+ if (f.endsWith('.xml')) entry.type = 'xml';
4713
+ // Extract class name from path
4714
+ const phpMatch = f.match(/\/([A-Z]\w+)\.php$/);
4715
+ if (phpMatch) entry.className = phpMatch[1];
4716
+ results.push(entry);
4717
+ }
4718
+ } catch {}
4719
+ }
4720
+
4647
4721
  const structure = {
4648
4722
  controllers: results.filter(r => r.isController || r.path?.includes('/Controller/')),
4649
4723
  models: results.filter(r => r.isModel || (r.path?.includes('/Model/') && !r.path?.includes('ResourceModel'))),
@@ -5490,9 +5564,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5490
5564
  const qr = `${a.className} ${ns}`.trim();
5491
5565
  const raw = await rustSearchAsync(qr, 30);
5492
5566
  const cl = a.className.toLowerCase();
5493
- const res = raw.map(normalizeResult).filter(r =>
5567
+ let res = raw.map(normalizeResult).filter(r =>
5494
5568
  r.className?.toLowerCase().includes(cl) || r.path?.toLowerCase().includes(cl.replace(/\\/g, '/'))
5495
5569
  );
5570
+ // Filesystem fallback for batch find_class
5571
+ if (res.length === 0 && config.magentoRoot) {
5572
+ const shortName = a.className.split('\\').pop();
5573
+ try {
5574
+ const files = await glob(`**/${shortName}.php`, { cwd: config.magentoRoot, absolute: false, nodir: true });
5575
+ for (const f of files.slice(0, 5)) {
5576
+ res.push({ path: f, className: shortName, score: 0.5 });
5577
+ }
5578
+ } catch {}
5579
+ }
5496
5580
  text = formatSearchResults(res.slice(0, 5));
5497
5581
  break;
5498
5582
  }