magector 2.7.1 → 2.8.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.
Files changed (2) hide show
  1. package/package.json +5 -5
  2. package/src/mcp-server.js +110 -43
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magector",
3
- "version": "2.7.1",
3
+ "version": "2.8.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.7.1",
37
- "@magector/cli-linux-x64": "2.7.1",
38
- "@magector/cli-linux-arm64": "2.7.1",
39
- "@magector/cli-win32-x64": "2.7.1"
36
+ "@magector/cli-darwin-arm64": "2.8.0",
37
+ "@magector/cli-linux-x64": "2.8.0",
38
+ "@magector/cli-linux-arm64": "2.8.0",
39
+ "@magector/cli-win32-x64": "2.8.0"
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, '\\$&');
@@ -4016,11 +4043,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4016
4043
  const reqStart = Date.now();
4017
4044
  logToFile('REQ', `${name}(${JSON.stringify(args || {})})`);
4018
4045
 
4019
- // ── Warmup guard: index compatibility check or serve process still loading ──
4046
+ // ── Warmup guard: only block if no DB exists at all (nothing to search) ──
4047
+ // Tools with filesystem fallback work without the serve process, so we
4048
+ // don't block them during warmup. Only block if the DB doesn't exist.
4020
4049
  const indexFreeTools = ['magento_stats', 'magento_analyze_diff', 'magento_complexity',
4021
4050
  'magento_trace_dependency', 'magento_error_parser', 'magento_find_layout',
4022
4051
  'magento_impact_analysis', 'magento_find_event_flow', 'magento_find_test',
4023
- 'magento_trace_data_flow', 'magento_find_event_dispatchers'];
4052
+ 'magento_trace_data_flow', 'magento_find_event_dispatchers',
4053
+ // These tools have filesystem/di.xml fallbacks — work without serve process
4054
+ 'magento_find_class', 'magento_find_method', 'magento_find_plugin',
4055
+ 'magento_find_observer', 'magento_find_di_wiring', 'magento_module_structure',
4056
+ 'magento_batch', 'magento_find_config', 'magento_find_callers'];
4024
4057
  if (warmupInProgress && !indexFreeTools.includes(name)) {
4025
4058
  logToFile('REQ', `${name} → blocked (warmup: loading index)`);
4026
4059
  return {
@@ -5789,13 +5822,34 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5789
5822
  case 'magento_module_structure': {
5790
5823
  const raw = await rustSearchAsync(a.moduleName, 200);
5791
5824
  const modulePath = a.moduleName.replace('_', '/') + '/';
5792
- const parts = a.moduleName.split('_');
5793
- const vendorPath = parts.length === 2 ? `module-${parts[1].toLowerCase()}/` : '';
5794
- const res = raw.map(normalizeResult).filter(r => {
5825
+ const mParts = a.moduleName.split('_');
5826
+ // Hyphenate camelCase for vendor path: OrderSplit order-split
5827
+ const vendorPath = mParts.length === 2
5828
+ ? `module-${mParts[1].replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}/`
5829
+ : '';
5830
+ let res = raw.map(normalizeResult).filter(r => {
5795
5831
  const p = r.path || '';
5796
5832
  const mod = r.module || '';
5797
5833
  return mod === a.moduleName || p.includes(modulePath) || (vendorPath && p.toLowerCase().includes(vendorPath));
5798
5834
  });
5835
+ // Filesystem fallback
5836
+ if (res.length === 0 && config.magentoRoot && vendorPath) {
5837
+ try {
5838
+ const vendorGlob = `**/${vendorPath}**/*.{php,xml,phtml}`;
5839
+ const files = await glob(vendorGlob, { cwd: config.magentoRoot, absolute: false, nodir: true });
5840
+ for (const f of files.slice(0, 100)) {
5841
+ const entry = { path: f, score: 0.5 };
5842
+ if (f.includes('/Controller/')) entry.isController = true;
5843
+ if (f.includes('/Model/')) entry.isModel = true;
5844
+ if (f.includes('/Plugin/')) entry.isPlugin = true;
5845
+ if (f.includes('/Observer/')) entry.isObserver = true;
5846
+ if (f.endsWith('.xml')) entry.type = 'xml';
5847
+ const phpMatch = f.match(/\/([A-Z]\w+)\.php$/);
5848
+ if (phpMatch) entry.className = phpMatch[1];
5849
+ res.push(entry);
5850
+ }
5851
+ } catch {}
5852
+ }
5799
5853
  text = `Module: ${a.moduleName} (${res.length} files)\n`;
5800
5854
  const cats = { controllers: '/Controller/', models: '/Model/', plugins: '/Plugin/', observers: '/Observer/', api: '/Api/' };
5801
5855
  for (const [cat, pattern] of Object.entries(cats)) {
@@ -5912,8 +5966,9 @@ async function main() {
5912
5966
  logToFile('WARN', `Serve process version mismatch: ${staleVersion} vs ${__pkg.version} — killing stale process`);
5913
5967
  console.error(`Killing stale serve process (version ${staleVersion}, current ${__pkg.version})`);
5914
5968
  killStaleServeProcess();
5915
- // Remove stale socket so we don't connect to it
5969
+ // Remove stale socket and lock so we don't connect to dead process
5916
5970
  try { if (existsSync(SOCK_PATH)) unlinkSync(SOCK_PATH); } catch {}
5971
+ releasePrimaryLock();
5917
5972
  }
5918
5973
 
5919
5974
  const connected = await tryConnectSocket();
@@ -5924,47 +5979,53 @@ async function main() {
5924
5979
  role = 'primary';
5925
5980
  logToFile('INFO', 'Acquired primary lock — this instance owns the serve process');
5926
5981
 
5927
- // Check DB format (uses cache instant if already validated)
5928
- if (existsSync(config.dbPath)) {
5929
- if (!(await checkDbFormat())) {
5930
- logToFile('WARN', 'Database format incompatible — scheduling background re-index');
5931
- startBackgroundReindex();
5932
- } else {
5933
- logToFile('INFO', 'Existing database is compatible — reusing index');
5934
- }
5935
- } else if (config.magentoRoot && existsSync(config.magentoRoot)) {
5936
- logToFile('INFO', 'No index database found — scheduling background index');
5937
- startBackgroundReindex();
5938
- }
5939
-
5940
- const canStartServe = !reindexInProgress || (existsSync(config.dbPath) && (() => { try { return statSync(config.dbPath).size > 100; } catch { return false; } })());
5941
- if (canStartServe) {
5982
+ // Start serve process in background don't block tool availability
5983
+ // Tools with filesystem fallbacks work immediately via execFileSync.
5984
+ // Serve process provides faster search once ready.
5985
+ (async () => {
5942
5986
  try {
5943
- startServeProcess();
5944
- if (serveReadyPromise) {
5945
- const ready = await Promise.race([
5946
- serveReadyPromise,
5947
- new Promise(r => setTimeout(() => r(false), 60000))
5948
- ]);
5949
- if (ready) {
5950
- logToFile('INFO', 'Serve process ready (primary)');
5951
- console.error('Serve process ready (primary)');
5952
- startSocketProxy();
5987
+ // Check DB format (uses cache → instant if already validated)
5988
+ if (existsSync(config.dbPath)) {
5989
+ if (!(await checkDbFormat())) {
5990
+ logToFile('WARN', 'Database format incompatible — scheduling background re-index');
5991
+ startBackgroundReindex();
5953
5992
  } else {
5954
- logToFile('WARN', 'Serve process not ready in time, will use fallback');
5955
- console.error('Serve process not ready in time, will use fallback');
5993
+ logToFile('INFO', 'Existing database is compatible reusing index');
5994
+ }
5995
+ } else if (config.magentoRoot && existsSync(config.magentoRoot)) {
5996
+ logToFile('INFO', 'No index database found — scheduling background index');
5997
+ startBackgroundReindex();
5998
+ }
5999
+
6000
+ const canStartServe = !reindexInProgress || (existsSync(config.dbPath) && (() => { try { return statSync(config.dbPath).size > 100; } catch { return false; } })());
6001
+ if (canStartServe) {
6002
+ startServeProcess();
6003
+ if (serveReadyPromise) {
6004
+ const ready = await Promise.race([
6005
+ serveReadyPromise,
6006
+ new Promise(r => setTimeout(() => r(false), 60000))
6007
+ ]);
6008
+ if (ready) {
6009
+ logToFile('INFO', 'Serve process ready (primary)');
6010
+ console.error('Serve process ready (primary)');
6011
+ startSocketProxy();
6012
+ } else {
6013
+ logToFile('WARN', 'Serve process not ready in time, will use fallback');
6014
+ console.error('Serve process not ready in time, will use fallback');
6015
+ }
5956
6016
  }
5957
6017
  }
5958
6018
  } catch {
5959
6019
  // Non-fatal: falls back to execFileSync per query
5960
6020
  }
5961
- }
6021
+ })();
5962
6022
  } else {
5963
- // Another instance is starting up — wait for its socket to appear
5964
- logToFile('INFO', 'Another instance is primary — waiting for socket...');
5965
- console.error('Waiting for primary instance to start serve process...');
5966
- for (let i = 0; i < 12; i++) { // wait up to 60s
5967
- await new Promise(r => setTimeout(r, 5000));
6023
+ // Another instance is starting up — try socket briefly, then fall through
6024
+ logToFile('INFO', 'Another instance is primary — trying socket...');
6025
+ console.error('Trying to join existing serve process...');
6026
+ // Quick check: 3 attempts at 2s intervals (6s max), then give up and use fallback
6027
+ for (let i = 0; i < 3; i++) {
6028
+ await new Promise(r => setTimeout(r, 2000));
5968
6029
  if (await tryConnectSocket()) {
5969
6030
  logToFile('INFO', 'Connected to socket after waiting (secondary)');
5970
6031
  console.error('Joined existing serve process (secondary)');
@@ -5972,15 +6033,21 @@ async function main() {
5972
6033
  }
5973
6034
  }
5974
6035
  if (!globalServeQuery) {
5975
- logToFile('WARN', 'Socket not available after waiting using cold-start fallback');
6036
+ logToFile('INFO', 'Socket not available tools will use cold-start fallback');
5976
6037
  }
5977
6038
  }
5978
6039
 
5979
- await loadDescriptions();
5980
- } finally {
6040
+ // Mark tools as available ASAP — filesystem fallbacks work without serve process
5981
6041
  warmupInProgress = false;
5982
- logToFile('INFO', 'Warmup complete all tools available');
6042
+ logToFile('INFO', 'Tools available (serve process loading in background)');
5983
6043
  console.error('Warmup complete — all tools available');
6044
+
6045
+ // Load descriptions in background (non-blocking)
6046
+ loadDescriptions().catch(() => {});
6047
+ } catch (err) {
6048
+ warmupInProgress = false;
6049
+ logToFile('WARN', `Startup error (tools still available via fallback): ${err.message}`);
6050
+ console.error('Warmup complete — all tools available (with fallbacks)');
5984
6051
  }
5985
6052
  }
5986
6053