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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magector",
3
- "version": "2.6.1",
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.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.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
- 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',
@@ -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
- const results = raw.map(normalizeResult).filter(r => {
4639
- const path = r.path || '';
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
- path.includes(modulePath) ||
4644
- (vendorPath && path.toLowerCase().includes(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
- const res = raw.map(normalizeResult).filter(r =>
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
  }