magector 2.6.0 → 2.6.1
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 +96 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magector",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.1",
|
|
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.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"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
42
42
|
"magento",
|
package/src/mcp-server.js
CHANGED
|
@@ -202,7 +202,16 @@ function releasePrimaryLock() {
|
|
|
202
202
|
* Write the serve process PID to disk so future instances can clean up orphans.
|
|
203
203
|
*/
|
|
204
204
|
function writePidFile(pid) {
|
|
205
|
-
try { writeFileSync(PID_PATH,
|
|
205
|
+
try { writeFileSync(PID_PATH, `${pid}\n${__pkg.version}`); } catch {}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function getServePidVersion() {
|
|
209
|
+
try {
|
|
210
|
+
if (!existsSync(PID_PATH)) return null;
|
|
211
|
+
const content = readFileSync(PID_PATH, 'utf-8').trim();
|
|
212
|
+
const lines = content.split('\n');
|
|
213
|
+
return lines[1] || null;
|
|
214
|
+
} catch { return null; }
|
|
206
215
|
}
|
|
207
216
|
|
|
208
217
|
function removePidFile() {
|
|
@@ -809,20 +818,29 @@ async function rustSearchAsync(query, limit = 10) {
|
|
|
809
818
|
if (queryFn) {
|
|
810
819
|
try {
|
|
811
820
|
const resp = await queryFn('search', { query, limit });
|
|
812
|
-
if (resp.ok && Array.isArray(resp.data)) {
|
|
821
|
+
if (resp.ok && Array.isArray(resp.data) && resp.data.length > 0) {
|
|
813
822
|
cacheSet(cacheKey, resp.data);
|
|
814
823
|
return resp.data;
|
|
815
824
|
}
|
|
825
|
+
// Serve returned empty results — fall through to execFileSync
|
|
826
|
+
// This catches stale serve processes with wrong/empty index
|
|
827
|
+
if (resp.ok && Array.isArray(resp.data) && resp.data.length === 0) {
|
|
828
|
+
logToFile('WARN', `Serve returned 0 results for "${query}" — trying execFileSync fallback`);
|
|
829
|
+
}
|
|
816
830
|
} catch (err) {
|
|
817
831
|
logToFile('WARN', `Serve query failed, falling back to execFileSync: ${err.message}`);
|
|
818
832
|
}
|
|
819
833
|
}
|
|
820
834
|
|
|
821
|
-
// Fallback: cold-start execFileSync
|
|
835
|
+
// Fallback: cold-start execFileSync (always works if CLI works)
|
|
822
836
|
logToFile('INFO', `Using execFileSync fallback for search: "${query}"`);
|
|
823
837
|
try {
|
|
824
838
|
const result = rustSearchSync(query, limit);
|
|
825
|
-
|
|
839
|
+
const arr = Array.isArray(result) ? result : [];
|
|
840
|
+
if (arr.length > 0) {
|
|
841
|
+
cacheSet(cacheKey, arr);
|
|
842
|
+
}
|
|
843
|
+
return arr;
|
|
826
844
|
} catch (err) {
|
|
827
845
|
logToFile('WARN', `execFileSync fallback failed: ${err.message}`);
|
|
828
846
|
return [];
|
|
@@ -4209,14 +4227,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4209
4227
|
const diFiles = await getDiXmlFiles(fpRoot);
|
|
4210
4228
|
// Normalize target class for matching (both \ and \\)
|
|
4211
4229
|
const normalizedTarget = args.targetClass.replace(/\\\\/g, '\\');
|
|
4230
|
+
const isFqcn = normalizedTarget.includes('\\');
|
|
4231
|
+
const shortTarget = normalizedTarget.split('\\').pop().toLowerCase();
|
|
4212
4232
|
for (const { content, relPath } of diFiles) {
|
|
4213
|
-
if (!content.includes(normalizedTarget)) continue;
|
|
4233
|
+
if (!content.includes(isFqcn ? normalizedTarget : args.targetClass)) continue;
|
|
4214
4234
|
// Find plugin registrations for this target
|
|
4215
4235
|
const typeBlockRegex = /<type\s+name="([^"]+)"[^>]*>([\s\S]*?)<\/type>/g;
|
|
4216
4236
|
let tm;
|
|
4217
4237
|
while ((tm = typeBlockRegex.exec(content)) !== null) {
|
|
4218
4238
|
const typeName = tm[1].replace(/\\\\/g, '\\');
|
|
4219
|
-
if
|
|
4239
|
+
// FQCN: exact match. Short name: match if type ends with the short name
|
|
4240
|
+
const typeMatches = isFqcn
|
|
4241
|
+
? typeName === normalizedTarget
|
|
4242
|
+
: typeName.split('\\').pop().toLowerCase() === shortTarget;
|
|
4243
|
+
if (!typeMatches) continue;
|
|
4220
4244
|
const block = tm[2];
|
|
4221
4245
|
const pluginRegex = /<plugin\s+([^/>]*)\/?>/g;
|
|
4222
4246
|
let pm;
|
|
@@ -4288,19 +4312,35 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4288
4312
|
}
|
|
4289
4313
|
|
|
4290
4314
|
case 'magento_find_observer': {
|
|
4291
|
-
|
|
4292
|
-
const
|
|
4293
|
-
let
|
|
4294
|
-
|
|
4295
|
-
)
|
|
4296
|
-
|
|
4315
|
+
// Primary: parse events.xml for exact event name match (structural, not semantic)
|
|
4316
|
+
const eventFlow = await traceEventFlow(args.eventName);
|
|
4317
|
+
let text = '';
|
|
4318
|
+
|
|
4319
|
+
if (eventFlow.observers.length > 0) {
|
|
4320
|
+
text += `### Observers for \`${args.eventName}\` (${eventFlow.observers.length})\n\n`;
|
|
4321
|
+
for (const obs of eventFlow.observers) {
|
|
4322
|
+
text += `- **${obs.name}** → \`${obs.instance}::${obs.method}()\` (${obs.file})\n`;
|
|
4323
|
+
}
|
|
4324
|
+
if (eventFlow.observerDetails.length > 0) {
|
|
4325
|
+
text += `\n### Observer PHP Files\n`;
|
|
4326
|
+
for (const det of eventFlow.observerDetails) {
|
|
4327
|
+
text += `- \`${det.instance}\` → ${det.path}\n`;
|
|
4328
|
+
}
|
|
4329
|
+
}
|
|
4330
|
+
}
|
|
4297
4331
|
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4332
|
+
// Fallback: semantic search if events.xml parsing found nothing
|
|
4333
|
+
if (eventFlow.observers.length === 0) {
|
|
4334
|
+
const query = `event ${args.eventName} observer`;
|
|
4335
|
+
const raw = await rustSearchAsync(query, 30);
|
|
4336
|
+
let results = raw.map(normalizeResult).filter(r =>
|
|
4337
|
+
r.isObserver || r.path?.includes('/Observer/') || r.path?.includes('events.xml')
|
|
4338
|
+
);
|
|
4339
|
+
results = rerank(results, { isObserver: true, pathContains: ['events.xml', '/Observer/'] });
|
|
4340
|
+
text = formatSearchResults(results.slice(0, 15));
|
|
4341
|
+
}
|
|
4342
|
+
|
|
4343
|
+
return { content: [{ type: 'text', text }] };
|
|
4304
4344
|
}
|
|
4305
4345
|
|
|
4306
4346
|
case 'magento_find_preference': {
|
|
@@ -5533,6 +5573,34 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
5533
5573
|
text = formatSearchResults(res.slice(0, 5));
|
|
5534
5574
|
break;
|
|
5535
5575
|
}
|
|
5576
|
+
case 'magento_module_structure': {
|
|
5577
|
+
const raw = await rustSearchAsync(a.moduleName, 200);
|
|
5578
|
+
const modulePath = a.moduleName.replace('_', '/') + '/';
|
|
5579
|
+
const parts = a.moduleName.split('_');
|
|
5580
|
+
const vendorPath = parts.length === 2 ? `module-${parts[1].toLowerCase()}/` : '';
|
|
5581
|
+
const res = raw.map(normalizeResult).filter(r => {
|
|
5582
|
+
const p = r.path || '';
|
|
5583
|
+
const mod = r.module || '';
|
|
5584
|
+
return mod === a.moduleName || p.includes(modulePath) || (vendorPath && p.toLowerCase().includes(vendorPath));
|
|
5585
|
+
});
|
|
5586
|
+
text = `Module: ${a.moduleName} (${res.length} files)\n`;
|
|
5587
|
+
const cats = { controllers: '/Controller/', models: '/Model/', plugins: '/Plugin/', observers: '/Observer/', api: '/Api/' };
|
|
5588
|
+
for (const [cat, pattern] of Object.entries(cats)) {
|
|
5589
|
+
const matches = res.filter(r => r.path?.includes(pattern));
|
|
5590
|
+
if (matches.length > 0) {
|
|
5591
|
+
text += `${cat}: ${matches.length} (${matches.slice(0, 3).map(r => r.className || r.path?.split('/').pop()).join(', ')})\n`;
|
|
5592
|
+
}
|
|
5593
|
+
}
|
|
5594
|
+
break;
|
|
5595
|
+
}
|
|
5596
|
+
case 'magento_find_observer': {
|
|
5597
|
+
const flow = await traceEventFlow(a.eventName);
|
|
5598
|
+
text = `Observers: ${flow.observers.length}\n`;
|
|
5599
|
+
for (const o of flow.observers.slice(0, 10)) {
|
|
5600
|
+
text += `- ${o.name}: ${o.instance}::${o.method}() (${o.file})\n`;
|
|
5601
|
+
}
|
|
5602
|
+
break;
|
|
5603
|
+
}
|
|
5536
5604
|
default:
|
|
5537
5605
|
text = `Unsupported batch tool: ${q.tool}`;
|
|
5538
5606
|
}
|
|
@@ -5625,6 +5693,16 @@ async function main() {
|
|
|
5625
5693
|
try {
|
|
5626
5694
|
let role = 'secondary';
|
|
5627
5695
|
|
|
5696
|
+
// Kill stale serve process if version mismatch (e.g., user upgraded Magector)
|
|
5697
|
+
const staleVersion = getServePidVersion();
|
|
5698
|
+
if (staleVersion && staleVersion !== __pkg.version) {
|
|
5699
|
+
logToFile('WARN', `Serve process version mismatch: ${staleVersion} vs ${__pkg.version} — killing stale process`);
|
|
5700
|
+
console.error(`Killing stale serve process (version ${staleVersion}, current ${__pkg.version})`);
|
|
5701
|
+
killStaleServeProcess();
|
|
5702
|
+
// Remove stale socket so we don't connect to it
|
|
5703
|
+
try { if (existsSync(SOCK_PATH)) unlinkSync(SOCK_PATH); } catch {}
|
|
5704
|
+
}
|
|
5705
|
+
|
|
5628
5706
|
const connected = await tryConnectSocket();
|
|
5629
5707
|
if (connected) {
|
|
5630
5708
|
logToFile('INFO', 'Joined existing serve process via socket (secondary)');
|