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.
- package/package.json +5 -5
- package/src/mcp-server.js +110 -43
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magector",
|
|
3
|
-
"version": "2.
|
|
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.
|
|
37
|
-
"@magector/cli-linux-x64": "2.
|
|
38
|
-
"@magector/cli-linux-arm64": "2.
|
|
39
|
-
"@magector/cli-win32-x64": "2.
|
|
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:
|
|
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
|
|
5793
|
-
|
|
5794
|
-
const
|
|
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
|
|
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
|
-
//
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
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
|
-
|
|
5944
|
-
if (
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
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('
|
|
5955
|
-
|
|
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 —
|
|
5964
|
-
logToFile('INFO', 'Another instance is primary —
|
|
5965
|
-
console.error('
|
|
5966
|
-
|
|
5967
|
-
|
|
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('
|
|
6036
|
+
logToFile('INFO', 'Socket not available — tools will use cold-start fallback');
|
|
5976
6037
|
}
|
|
5977
6038
|
}
|
|
5978
6039
|
|
|
5979
|
-
|
|
5980
|
-
} finally {
|
|
6040
|
+
// Mark tools as available ASAP — filesystem fallbacks work without serve process
|
|
5981
6041
|
warmupInProgress = false;
|
|
5982
|
-
logToFile('INFO', '
|
|
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
|
|