magector 2.5.0 → 2.5.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 +254 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magector",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.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.5.
|
|
37
|
-
"@magector/cli-linux-x64": "2.5.
|
|
38
|
-
"@magector/cli-linux-arm64": "2.5.
|
|
39
|
-
"@magector/cli-win32-x64": "2.5.
|
|
36
|
+
"@magector/cli-darwin-arm64": "2.5.1",
|
|
37
|
+
"@magector/cli-linux-x64": "2.5.1",
|
|
38
|
+
"@magector/cli-linux-arm64": "2.5.1",
|
|
39
|
+
"@magector/cli-win32-x64": "2.5.1"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
42
42
|
"magento",
|
package/src/mcp-server.js
CHANGED
|
@@ -2176,6 +2176,7 @@ async function analyzeImpact(className) {
|
|
|
2176
2176
|
diXmlReferences: [],
|
|
2177
2177
|
instantiations: [],
|
|
2178
2178
|
typeHints: [],
|
|
2179
|
+
runtimeCallers: [],
|
|
2179
2180
|
total: 0
|
|
2180
2181
|
};
|
|
2181
2182
|
|
|
@@ -2194,6 +2195,7 @@ async function analyzeImpact(className) {
|
|
|
2194
2195
|
];
|
|
2195
2196
|
|
|
2196
2197
|
// Check PHP files for direct references
|
|
2198
|
+
const escapedShort = shortName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
2197
2199
|
for (const r of relatedPaths.slice(0, 40)) {
|
|
2198
2200
|
const absPath = path.join(root, r.path);
|
|
2199
2201
|
if (!existsSync(absPath) || !r.path.endsWith('.php')) continue;
|
|
@@ -2208,13 +2210,38 @@ async function analyzeImpact(className) {
|
|
|
2208
2210
|
references.instantiations.push({ file: r.path, className: r.className });
|
|
2209
2211
|
}
|
|
2210
2212
|
if (content.includes(`@var ${shortName}`) || content.includes(`@param ${shortName}`) ||
|
|
2211
|
-
content.match(new RegExp(`:\\s*${
|
|
2213
|
+
content.match(new RegExp(`:\\s*${escapedShort}\\b`))) {
|
|
2212
2214
|
references.typeHints.push({ file: r.path, className: r.className });
|
|
2213
2215
|
}
|
|
2216
|
+
|
|
2217
|
+
// Runtime callers: find $this->property->method() where property is typed as this class
|
|
2218
|
+
if (hasUse) {
|
|
2219
|
+
const ctorMatch = content.match(/function\s+__construct\s*\(([\s\S]*?)\)\s*[{:]/);
|
|
2220
|
+
if (ctorMatch) {
|
|
2221
|
+
// Find constructor params typed as the target class
|
|
2222
|
+
const paramRegex = new RegExp(`(?:${escapedShort}|${className.replace(/\\/g, '\\\\')})\\s+\\$(\\w+)`, 'g');
|
|
2223
|
+
let pm;
|
|
2224
|
+
while ((pm = paramRegex.exec(ctorMatch[1])) !== null) {
|
|
2225
|
+
const propName = pm[1];
|
|
2226
|
+
// Find all calls to this property in the file
|
|
2227
|
+
const callRegex = new RegExp(`\\$this->${propName}->(\\w+)\\s*\\(`, 'g');
|
|
2228
|
+
let cm;
|
|
2229
|
+
while ((cm = callRegex.exec(content)) !== null) {
|
|
2230
|
+
references.runtimeCallers.push({
|
|
2231
|
+
file: r.path,
|
|
2232
|
+
callerClass: r.className,
|
|
2233
|
+
property: propName,
|
|
2234
|
+
calledMethod: cm[1]
|
|
2235
|
+
});
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2214
2240
|
}
|
|
2215
2241
|
|
|
2216
2242
|
references.total = references.useStatements.length + references.diXmlReferences.length +
|
|
2217
|
-
references.instantiations.length + references.typeHints.length
|
|
2243
|
+
references.instantiations.length + references.typeHints.length +
|
|
2244
|
+
references.runtimeCallers.length;
|
|
2218
2245
|
|
|
2219
2246
|
return references;
|
|
2220
2247
|
}
|
|
@@ -2904,6 +2931,25 @@ async function traceCallChain(startClass, startMethod, maxDepth = 3) {
|
|
|
2904
2931
|
};
|
|
2905
2932
|
|
|
2906
2933
|
const classFileMap = new Map();
|
|
2934
|
+
const parentClassCache = new Map();
|
|
2935
|
+
|
|
2936
|
+
// Resolve parent class from extends declaration in PHP file content
|
|
2937
|
+
function resolveParentFromContent(content) {
|
|
2938
|
+
const extendsMatch = content.match(/class\s+\w+\s+extends\s+([\w\\]+)/);
|
|
2939
|
+
if (!extendsMatch) return null;
|
|
2940
|
+
const parent = extendsMatch[1];
|
|
2941
|
+
// If it's a short name, resolve using use statements
|
|
2942
|
+
if (!parent.includes('\\')) {
|
|
2943
|
+
const useMatch = content.match(new RegExp(`use\\s+([\\w\\\\]+\\\\${parent})\\s*;`));
|
|
2944
|
+
if (useMatch) return useMatch[1];
|
|
2945
|
+
// Check namespace-relative
|
|
2946
|
+
const nsMatch = content.match(/namespace\s+([\w\\]+)/);
|
|
2947
|
+
if (nsMatch) return `${nsMatch[1]}\\${parent}`;
|
|
2948
|
+
return parent;
|
|
2949
|
+
}
|
|
2950
|
+
// Leading backslash = fully qualified
|
|
2951
|
+
return parent.replace(/^\\/, '');
|
|
2952
|
+
}
|
|
2907
2953
|
|
|
2908
2954
|
async function resolveClassFile(className) {
|
|
2909
2955
|
const shortName = className.split('\\').pop();
|
|
@@ -3009,14 +3055,46 @@ async function traceCallChain(startClass, startMethod, maxDepth = 3) {
|
|
|
3009
3055
|
const relativePath = filePath.replace(root + '/', '');
|
|
3010
3056
|
|
|
3011
3057
|
// Extract method body (brace counting)
|
|
3058
|
+
const escapedMethod = methodName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3012
3059
|
const methodRegex = new RegExp(
|
|
3013
|
-
`function\\s+${
|
|
3060
|
+
`function\\s+${escapedMethod}\\s*\\([^)]*\\)[^{]*\\{`
|
|
3014
3061
|
);
|
|
3015
|
-
|
|
3062
|
+
let methodStart = content.search(methodRegex);
|
|
3063
|
+
|
|
3064
|
+
// If method not found in this class, walk up the inheritance chain
|
|
3065
|
+
let resolvedClass = className;
|
|
3066
|
+
let resolvedContent = content;
|
|
3067
|
+
let resolvedFilePath = filePath;
|
|
3016
3068
|
if (methodStart === -1) {
|
|
3017
|
-
|
|
3018
|
-
|
|
3069
|
+
let currentContent = content;
|
|
3070
|
+
let found = false;
|
|
3071
|
+
const visited = new Set([className]);
|
|
3072
|
+
for (let i = 0; i < 10; i++) { // max 10 parent levels
|
|
3073
|
+
const parentFqcn = resolveParentFromContent(currentContent);
|
|
3074
|
+
if (!parentFqcn || visited.has(parentFqcn)) break;
|
|
3075
|
+
visited.add(parentFqcn);
|
|
3076
|
+
const parentFile = await resolveClassFile(parentFqcn);
|
|
3077
|
+
if (!parentFile) break;
|
|
3078
|
+
let parentContent;
|
|
3079
|
+
try { parentContent = readFileSync(parentFile, 'utf-8'); } catch { break; }
|
|
3080
|
+
const parentMethodStart = parentContent.search(methodRegex);
|
|
3081
|
+
if (parentMethodStart !== -1) {
|
|
3082
|
+
resolvedClass = parentFqcn;
|
|
3083
|
+
resolvedContent = parentContent;
|
|
3084
|
+
resolvedFilePath = parentFile;
|
|
3085
|
+
methodStart = parentMethodStart;
|
|
3086
|
+
found = true;
|
|
3087
|
+
break;
|
|
3088
|
+
}
|
|
3089
|
+
currentContent = parentContent;
|
|
3090
|
+
}
|
|
3091
|
+
if (!found) {
|
|
3092
|
+
result.chain.push({ depth, class: className, method: methodName, file: relativePath, status: 'method_not_found' });
|
|
3093
|
+
return;
|
|
3094
|
+
}
|
|
3019
3095
|
}
|
|
3096
|
+
content = resolvedContent;
|
|
3097
|
+
const resolvedRelPath = resolvedFilePath.replace(root + '/', '');
|
|
3020
3098
|
|
|
3021
3099
|
let braceCount = 0;
|
|
3022
3100
|
let bodyStart = content.indexOf('{', methodStart);
|
|
@@ -3028,8 +3106,11 @@ async function traceCallChain(startClass, startMethod, maxDepth = 3) {
|
|
|
3028
3106
|
}
|
|
3029
3107
|
const methodBody = content.slice(bodyStart, bodyEnd + 1);
|
|
3030
3108
|
|
|
3109
|
+
// Show where method was actually found if inherited
|
|
3110
|
+
const inheritedFrom = (resolvedClass !== className) ? resolvedClass : null;
|
|
3031
3111
|
const chainEntry = {
|
|
3032
3112
|
depth, class: className, method: methodName, file: relativePath,
|
|
3113
|
+
...(inheritedFrom ? { inheritedFrom, resolvedFile: resolvedRelPath } : {}),
|
|
3033
3114
|
calls: [], dispatches: []
|
|
3034
3115
|
};
|
|
3035
3116
|
|
|
@@ -3049,13 +3130,20 @@ async function traceCallChain(startClass, startMethod, maxDepth = 3) {
|
|
|
3049
3130
|
while ((dc = depCallRegex.exec(methodBody)) !== null) {
|
|
3050
3131
|
const property = dc[1];
|
|
3051
3132
|
const calledMethod = dc[2];
|
|
3052
|
-
// Resolve property type from constructor
|
|
3053
|
-
const ctorMatch = content.match(/function\s+__construct\s*\(([\s\S]*?)\)\s*[{:]/);
|
|
3133
|
+
// Resolve property type from constructor — check the original class first, then the resolved (parent) class
|
|
3054
3134
|
let resolvedType = null;
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3135
|
+
let originalContent = content;
|
|
3136
|
+
if (resolvedClass !== className) {
|
|
3137
|
+
try { originalContent = readFileSync(filePath, 'utf-8'); } catch { originalContent = content; }
|
|
3138
|
+
}
|
|
3139
|
+
const contentSources = (resolvedClass !== className) ? [originalContent, content] : [content];
|
|
3140
|
+
for (const src of contentSources) {
|
|
3141
|
+
const ctorMatch = src.match(/function\s+__construct\s*\(([\s\S]*?)\)\s*[{:]/);
|
|
3142
|
+
if (ctorMatch) {
|
|
3143
|
+
const paramRegex = new RegExp(`([\\w\\\\]+)\\s+\\$${property}\\b`);
|
|
3144
|
+
const pm = ctorMatch[1].match(paramRegex);
|
|
3145
|
+
if (pm) { resolvedType = pm[1]; break; }
|
|
3146
|
+
}
|
|
3059
3147
|
}
|
|
3060
3148
|
chainEntry.calls.push({ type: 'dependency', property, method: calledMethod, typeHint: resolvedType || null });
|
|
3061
3149
|
}
|
|
@@ -3144,6 +3232,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
3144
3232
|
description: 'Enable query expansion with Magento domain synonyms for better recall (default: true)',
|
|
3145
3233
|
default: true
|
|
3146
3234
|
},
|
|
3235
|
+
precise: {
|
|
3236
|
+
type: 'boolean',
|
|
3237
|
+
description: 'Precise mode for debugging: disables query expansion AND applies strict post-filtering — only returns results where the file content contains at least one query keyword. Use for specific debugging queries like "gift card subtotal infinite loop". Default: false.',
|
|
3238
|
+
default: false
|
|
3239
|
+
},
|
|
3147
3240
|
},
|
|
3148
3241
|
required: ['query']
|
|
3149
3242
|
}
|
|
@@ -3759,6 +3852,29 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
3759
3852
|
required: ['eventName']
|
|
3760
3853
|
}
|
|
3761
3854
|
},
|
|
3855
|
+
{
|
|
3856
|
+
name: 'magento_batch',
|
|
3857
|
+
description: 'Execute multiple Magector tool calls in a single request to reduce MCP round-trip overhead. Each query runs in parallel and returns combined results. Use this when you need 2+ independent lookups (e.g., find a class AND its plugins AND its observers in one call instead of three).',
|
|
3858
|
+
inputSchema: {
|
|
3859
|
+
type: 'object',
|
|
3860
|
+
properties: {
|
|
3861
|
+
queries: {
|
|
3862
|
+
type: 'array',
|
|
3863
|
+
description: 'Array of tool calls to execute. Each entry has a "tool" name and "args" object.',
|
|
3864
|
+
items: {
|
|
3865
|
+
type: 'object',
|
|
3866
|
+
properties: {
|
|
3867
|
+
tool: { type: 'string', description: 'Magector tool name (e.g., "magento_find_class", "magento_find_plugin")' },
|
|
3868
|
+
args: { type: 'object', description: 'Arguments for the tool call' }
|
|
3869
|
+
},
|
|
3870
|
+
required: ['tool', 'args']
|
|
3871
|
+
},
|
|
3872
|
+
maxItems: 10
|
|
3873
|
+
}
|
|
3874
|
+
},
|
|
3875
|
+
required: ['queries']
|
|
3876
|
+
}
|
|
3877
|
+
},
|
|
3762
3878
|
]
|
|
3763
3879
|
}));
|
|
3764
3880
|
|
|
@@ -3805,12 +3921,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3805
3921
|
try {
|
|
3806
3922
|
switch (name) {
|
|
3807
3923
|
case 'magento_search': {
|
|
3808
|
-
const
|
|
3809
|
-
const
|
|
3924
|
+
const precise = args.precise === true;
|
|
3925
|
+
const searchQuery = (args.expand !== false && !precise) ? expandQuery(args.query) : args.query;
|
|
3926
|
+
const raw = await rustSearchAsync(searchQuery, Math.max(args.limit || 10, precise ? 60 : 30));
|
|
3810
3927
|
const arr = Array.isArray(raw) ? raw : [];
|
|
3811
3928
|
let results = arr.map(normalizeResult);
|
|
3812
3929
|
// Hybrid BM25 rerank for better exact-match handling
|
|
3813
3930
|
results = hybridRerank(results, args.query);
|
|
3931
|
+
// Precise mode: strict post-filter — result must contain at least one significant query keyword
|
|
3932
|
+
if (precise) {
|
|
3933
|
+
const queryTerms = args.query.toLowerCase().split(/\s+/).filter(t => t.length > 2);
|
|
3934
|
+
results = results.filter(r => {
|
|
3935
|
+
const haystack = [r.searchText, r.className, r.methodName, r.path, ...(r.methods || [])]
|
|
3936
|
+
.filter(Boolean).join(' ').toLowerCase();
|
|
3937
|
+
return queryTerms.some(term => haystack.includes(term));
|
|
3938
|
+
});
|
|
3939
|
+
}
|
|
3814
3940
|
// Apply module filter if specified
|
|
3815
3941
|
if (args.moduleFilter) {
|
|
3816
3942
|
results = filterByModule(results, args.moduleFilter);
|
|
@@ -4850,6 +4976,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4850
4976
|
text += '\n';
|
|
4851
4977
|
}
|
|
4852
4978
|
|
|
4979
|
+
if (impact.runtimeCallers.length > 0) {
|
|
4980
|
+
text += `### Runtime Callers (${impact.runtimeCallers.length})\n`;
|
|
4981
|
+
text += '_Classes that inject this class and call its methods at runtime:_\n';
|
|
4982
|
+
// Group by caller class for readability
|
|
4983
|
+
const grouped = new Map();
|
|
4984
|
+
for (const c of impact.runtimeCallers) {
|
|
4985
|
+
const key = c.callerClass || c.file;
|
|
4986
|
+
if (!grouped.has(key)) grouped.set(key, { file: c.file, methods: new Set() });
|
|
4987
|
+
grouped.get(key).methods.add(`$this->${c.property}->${c.calledMethod}()`);
|
|
4988
|
+
}
|
|
4989
|
+
for (const [cls, info] of grouped) {
|
|
4990
|
+
text += `- **\`${cls}\`** (\`${info.file}\`)\n`;
|
|
4991
|
+
for (const m of info.methods) {
|
|
4992
|
+
text += ` - \`${m}\`\n`;
|
|
4993
|
+
}
|
|
4994
|
+
}
|
|
4995
|
+
text += '\n';
|
|
4996
|
+
}
|
|
4997
|
+
|
|
4853
4998
|
if (impact.total === 0) {
|
|
4854
4999
|
text += '_No references found. The class may be referenced under a different name or alias._\n';
|
|
4855
5000
|
}
|
|
@@ -5057,6 +5202,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
5057
5202
|
const status = entry.status ? ` [${entry.status}]` : '';
|
|
5058
5203
|
text += `${indent}- **${entry.class}::${entry.method}**${status}`;
|
|
5059
5204
|
if (entry.file) text += ` (${entry.file})`;
|
|
5205
|
+
if (entry.inheritedFrom) text += `\n${indent} _inherited from_ \`${entry.inheritedFrom}\` (${entry.resolvedFile})`;
|
|
5060
5206
|
text += '\n';
|
|
5061
5207
|
|
|
5062
5208
|
if (entry.calls) {
|
|
@@ -5170,6 +5316,100 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
5170
5316
|
return { content: [{ type: 'text', text }] };
|
|
5171
5317
|
}
|
|
5172
5318
|
|
|
5319
|
+
case 'magento_batch': {
|
|
5320
|
+
const queries = args.queries || [];
|
|
5321
|
+
if (queries.length === 0) {
|
|
5322
|
+
return { content: [{ type: 'text', text: 'No queries provided.' }] };
|
|
5323
|
+
}
|
|
5324
|
+
if (queries.length > 10) {
|
|
5325
|
+
return { content: [{ type: 'text', text: 'Maximum 10 queries per batch.' }], isError: true };
|
|
5326
|
+
}
|
|
5327
|
+
// Run batch queries in parallel using existing standalone functions
|
|
5328
|
+
const batchResults = await Promise.all(queries.map(async (q, idx) => {
|
|
5329
|
+
try {
|
|
5330
|
+
const a = q.args || {};
|
|
5331
|
+
let text = '';
|
|
5332
|
+
switch (q.tool) {
|
|
5333
|
+
case 'magento_find_class': {
|
|
5334
|
+
const ns = a.namespace || '';
|
|
5335
|
+
const qr = `${a.className} ${ns}`.trim();
|
|
5336
|
+
const raw = await rustSearchAsync(qr, 30);
|
|
5337
|
+
const cl = a.className.toLowerCase();
|
|
5338
|
+
const res = raw.map(normalizeResult).filter(r =>
|
|
5339
|
+
r.className?.toLowerCase().includes(cl) || r.path?.toLowerCase().includes(cl.replace(/\\/g, '/'))
|
|
5340
|
+
);
|
|
5341
|
+
text = formatSearchResults(res.slice(0, 5));
|
|
5342
|
+
break;
|
|
5343
|
+
}
|
|
5344
|
+
case 'magento_find_plugin': {
|
|
5345
|
+
const qr = `plugin interceptor ${a.targetClass || ''} ${a.targetMethod || ''}`.trim();
|
|
5346
|
+
const raw = await rustSearchAsync(qr, 30);
|
|
5347
|
+
const res = raw.map(normalizeResult).filter(r =>
|
|
5348
|
+
r.magentoType === 'plugin' || r.path?.toLowerCase().includes('plugin')
|
|
5349
|
+
);
|
|
5350
|
+
text = formatSearchResults(res.slice(0, 10));
|
|
5351
|
+
break;
|
|
5352
|
+
}
|
|
5353
|
+
case 'magento_find_observer': {
|
|
5354
|
+
const flow = await traceEventFlow(a.eventName);
|
|
5355
|
+
text = `Observers: ${flow.observers.length}\n`;
|
|
5356
|
+
for (const o of flow.observers.slice(0, 10)) {
|
|
5357
|
+
text += `- ${o.name}: ${o.instance}::${o.method} (${o.file})\n`;
|
|
5358
|
+
}
|
|
5359
|
+
break;
|
|
5360
|
+
}
|
|
5361
|
+
case 'magento_trace_dependency': {
|
|
5362
|
+
const dep = await traceDependency(a.className, a.direction || 'both');
|
|
5363
|
+
text = `Preferences: ${dep.preferences.length}, Plugins: ${dep.plugins.length}, VirtualTypes: ${dep.virtualTypes.length}, Args: ${dep.argumentOverrides.length}\n`;
|
|
5364
|
+
for (const p of dep.plugins.slice(0, 5)) text += `- plugin: ${p.pluginName} → ${p.pluginClass}\n`;
|
|
5365
|
+
for (const p of dep.preferences.slice(0, 5)) text += `- pref: ${p.for} → ${p.type}\n`;
|
|
5366
|
+
break;
|
|
5367
|
+
}
|
|
5368
|
+
case 'magento_impact_analysis': {
|
|
5369
|
+
const imp = await analyzeImpact(a.className);
|
|
5370
|
+
text = `Total: ${imp.total} (use: ${imp.useStatements.length}, di: ${imp.diXmlReferences.length}, new: ${imp.instantiations.length}, type: ${imp.typeHints.length}, runtime: ${imp.runtimeCallers.length})\n`;
|
|
5371
|
+
for (const c of imp.runtimeCallers.slice(0, 5)) text += `- ${c.callerClass}: $this->${c.property}->${c.calledMethod}()\n`;
|
|
5372
|
+
break;
|
|
5373
|
+
}
|
|
5374
|
+
case 'magento_search': {
|
|
5375
|
+
const precise = a.precise === true;
|
|
5376
|
+
const sq = (a.expand !== false && !precise) ? expandQuery(a.query) : a.query;
|
|
5377
|
+
const raw = await rustSearchAsync(sq, 30);
|
|
5378
|
+
let res = raw.map(normalizeResult);
|
|
5379
|
+
res = hybridRerank(res, a.query);
|
|
5380
|
+
text = formatSearchResults(res.slice(0, a.limit || 5));
|
|
5381
|
+
break;
|
|
5382
|
+
}
|
|
5383
|
+
case 'magento_find_callers': {
|
|
5384
|
+
const callers = await findCallers(a.methodName, a.className);
|
|
5385
|
+
text = `Callers: ${callers.callers?.length || 0}\n`;
|
|
5386
|
+
for (const c of (callers.callers || []).slice(0, 10)) {
|
|
5387
|
+
text += `- ${c.class}::${c.method} (${c.path}:${c.line})\n`;
|
|
5388
|
+
}
|
|
5389
|
+
break;
|
|
5390
|
+
}
|
|
5391
|
+
case 'magento_find_event_flow': {
|
|
5392
|
+
const flow = await traceEventFlow(a.eventName);
|
|
5393
|
+
text = `Dispatchers: ${flow.dispatchers.length}, Observers: ${flow.observers.length}\n`;
|
|
5394
|
+
for (const o of flow.observers.slice(0, 10)) text += `- ${o.name}: ${o.instance} (${o.file})\n`;
|
|
5395
|
+
break;
|
|
5396
|
+
}
|
|
5397
|
+
default:
|
|
5398
|
+
text = `Unsupported batch tool: ${q.tool}`;
|
|
5399
|
+
}
|
|
5400
|
+
return { idx, tool: q.tool, text };
|
|
5401
|
+
} catch (err) {
|
|
5402
|
+
return { idx, tool: q.tool, text: `Error: ${err.message}` };
|
|
5403
|
+
}
|
|
5404
|
+
}));
|
|
5405
|
+
|
|
5406
|
+
let text = `## Batch Results (${batchResults.length} queries)\n\n`;
|
|
5407
|
+
for (const br of batchResults) {
|
|
5408
|
+
text += `---\n### [${br.idx + 1}] ${br.tool}\n\n${br.text}\n\n`;
|
|
5409
|
+
}
|
|
5410
|
+
return { content: [{ type: 'text', text }] };
|
|
5411
|
+
}
|
|
5412
|
+
|
|
5173
5413
|
default:
|
|
5174
5414
|
return {
|
|
5175
5415
|
content: [{
|