magector 2.9.0 → 2.10.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 +156 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magector",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.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.10.0",
|
|
37
|
+
"@magector/cli-linux-x64": "2.10.0",
|
|
38
|
+
"@magector/cli-linux-arm64": "2.10.0",
|
|
39
|
+
"@magector/cli-win32-x64": "2.10.0"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
42
42
|
"magento",
|
package/src/mcp-server.js
CHANGED
|
@@ -1855,6 +1855,83 @@ const ERROR_PATTERNS = [
|
|
|
1855
1855
|
type: 'invalid_plugin_method',
|
|
1856
1856
|
extract: (m) => ({ class: m[1], type: m[2], method: m[3] }),
|
|
1857
1857
|
suggestion: (ctx) => `Plugin method ${ctx.type}${ctx.method} in ${ctx.class} doesn't match any public method on the target class. Check method name spelling.`
|
|
1858
|
+
},
|
|
1859
|
+
// ── Business logic / configuration patterns ──
|
|
1860
|
+
{
|
|
1861
|
+
pattern: /rule\s+(?:did\s+)?not\s+(?:match|apply|work)|(?:sales|cart|price)\s*rule.*(?:not\s+applied|didn'?t\s+match|failed)/i,
|
|
1862
|
+
type: 'rule_not_matching',
|
|
1863
|
+
extract: () => ({}),
|
|
1864
|
+
suggestion: () => [
|
|
1865
|
+
'Sales/cart price rule not matching. Check BOTH code AND configuration:',
|
|
1866
|
+
'',
|
|
1867
|
+
'**Configuration checks (most common cause):**',
|
|
1868
|
+
'- Rule Actions tab: are ALL required shipping methods selected?',
|
|
1869
|
+
'- Rule Conditions: does the cart actually meet ALL conditions (customer group, subtotal threshold, date range)?',
|
|
1870
|
+
'- Rule status: is it Active? Check date range (from/to).',
|
|
1871
|
+
'- Rule priority/stop processing: is a higher-priority rule stopping this one?',
|
|
1872
|
+
'- Coupon: if coupon-based, was the correct coupon applied?',
|
|
1873
|
+
'- Website scope: is the rule assigned to the correct website?',
|
|
1874
|
+
'',
|
|
1875
|
+
'**Code checks (less common):**',
|
|
1876
|
+
'- Custom condition types: check classes extending AbstractCondition in vendor/',
|
|
1877
|
+
'- Plugins on Magento\\SalesRule\\Model\\Utility::canProcessRule() that may skip rules',
|
|
1878
|
+
'- Plugins on Magento\\SalesRule\\Model\\Rule\\Condition\\Address::validate()',
|
|
1879
|
+
'- DI preference overrides on Condition\\Address (e.g., marketplace container conditions)',
|
|
1880
|
+
'- The condition_type stored in conditions_serialized — does the PHP class exist and validate correctly?',
|
|
1881
|
+
'',
|
|
1882
|
+
'Use magento_grep to find: custom conditions, plugins on validate/canProcessRule, and DI preferences on Address.',
|
|
1883
|
+
'',
|
|
1884
|
+
'**Ask the user for DB data:**',
|
|
1885
|
+
'SELECT rule_id, name, conditions_serialized, actions_serialized, is_active, from_date, to_date',
|
|
1886
|
+
'FROM salesrule WHERE rule_id = <ID>;',
|
|
1887
|
+
'This lets you verify the exact condition_type classes and action configuration without guessing.'
|
|
1888
|
+
].join('\n')
|
|
1889
|
+
},
|
|
1890
|
+
{
|
|
1891
|
+
pattern: /free\s*shipping.*(?:not|didn'?t|did\s+not)\s+(?:apply|work|match)|(?:not|didn'?t)\s+(?:get|receive)\s+free\s*shipping/i,
|
|
1892
|
+
type: 'free_shipping_not_applied',
|
|
1893
|
+
extract: () => ({}),
|
|
1894
|
+
suggestion: () => [
|
|
1895
|
+
'Free shipping not applied. Most common causes:',
|
|
1896
|
+
'',
|
|
1897
|
+
'**1. Rule Actions — shipping method not selected (MOST COMMON)**',
|
|
1898
|
+
' The free shipping rule must have the specific shipping method checked in Actions.',
|
|
1899
|
+
' If "home delivery" is not selected but the customer chose home delivery → rule won\'t apply.',
|
|
1900
|
+
'',
|
|
1901
|
+
'**2. Condition threshold mismatch**',
|
|
1902
|
+
' Check which subtotal attribute the condition uses: base_subtotal, subtotal_incl_tax,',
|
|
1903
|
+
' drmax_free_shipping_price (custom). Each calculates differently (with/without tax, discounts).',
|
|
1904
|
+
'',
|
|
1905
|
+
'**3. Custom condition type**',
|
|
1906
|
+
' Container attributes (SubtotalWithDiscountInclTax) aggregate per shop type.',
|
|
1907
|
+
' If condition uses :1p suffix instead of :whole, only 1P items count.',
|
|
1908
|
+
'',
|
|
1909
|
+
'**4. Plugin interference**',
|
|
1910
|
+
' Check plugins on Utility::canProcessRule() and Carrier::collectRates().',
|
|
1911
|
+
'',
|
|
1912
|
+
'Start with: check the rule\'s Actions tab in admin for shipping method selection.',
|
|
1913
|
+
'',
|
|
1914
|
+
'**Ask the user for DB data:**',
|
|
1915
|
+
'SELECT rule_id, name, conditions_serialized, actions_serialized, simple_free_shipping',
|
|
1916
|
+
'FROM salesrule WHERE rule_id = <ID>;',
|
|
1917
|
+
'The actions_serialized will show which shipping methods are enabled for free shipping.'
|
|
1918
|
+
].join('\n')
|
|
1919
|
+
},
|
|
1920
|
+
{
|
|
1921
|
+
pattern: /(?:condition|rule|promotion|discount).*(?:custom|type).*(?:not|fail|wrong|weird|unexpected)/i,
|
|
1922
|
+
type: 'custom_condition_issue',
|
|
1923
|
+
extract: () => ({}),
|
|
1924
|
+
suggestion: () => [
|
|
1925
|
+
'Possible custom condition type issue. Investigate:',
|
|
1926
|
+
'',
|
|
1927
|
+
'**1. Check the rule configuration FIRST** — most "condition not working" bugs are misconfiguration.',
|
|
1928
|
+
'**2. Find custom condition classes:** grep for "extends AbstractCondition" in vendor/',
|
|
1929
|
+
'**3. Check DI preference on Condition\\Address** — marketplace modules often override validate().',
|
|
1930
|
+
'**4. Check if new attributes are registered in the Address override\'s switch/mapping.**',
|
|
1931
|
+
'**5. Verify condition_type in DB:** the serialized condition must reference an existing PHP class.',
|
|
1932
|
+
'',
|
|
1933
|
+
'If code analysis finds no bugs, the root cause is likely rule configuration in admin panel.'
|
|
1934
|
+
].join('\n')
|
|
1858
1935
|
}
|
|
1859
1936
|
];
|
|
1860
1937
|
|
|
@@ -4066,13 +4143,35 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
4066
4143
|
},
|
|
4067
4144
|
context: {
|
|
4068
4145
|
type: 'number',
|
|
4069
|
-
description: 'Lines of context around each match (default:
|
|
4070
|
-
default:
|
|
4146
|
+
description: 'Lines of context around each match (default: 2). Like grep -C.',
|
|
4147
|
+
default: 2
|
|
4071
4148
|
}
|
|
4072
4149
|
},
|
|
4073
4150
|
required: ['pattern']
|
|
4074
4151
|
}
|
|
4075
4152
|
},
|
|
4153
|
+
{
|
|
4154
|
+
name: 'magento_read',
|
|
4155
|
+
description: 'Read a file from the Magento codebase. Use in magento_batch to read multiple files in a single MCP call (e.g., grep finds 5 files → read all 5 in one batch). Supports line ranges for large files.',
|
|
4156
|
+
inputSchema: {
|
|
4157
|
+
type: 'object',
|
|
4158
|
+
properties: {
|
|
4159
|
+
path: {
|
|
4160
|
+
type: 'string',
|
|
4161
|
+
description: 'File path relative to MAGENTO_ROOT. Example: "vendor/acme/module-sales/Model/OrderService.php"'
|
|
4162
|
+
},
|
|
4163
|
+
startLine: {
|
|
4164
|
+
type: 'number',
|
|
4165
|
+
description: 'Start reading from this line number (1-based). Default: 1 (beginning of file).'
|
|
4166
|
+
},
|
|
4167
|
+
endLine: {
|
|
4168
|
+
type: 'number',
|
|
4169
|
+
description: 'Stop reading at this line number (inclusive). Default: end of file. Use with startLine for large files.'
|
|
4170
|
+
}
|
|
4171
|
+
},
|
|
4172
|
+
required: ['path']
|
|
4173
|
+
}
|
|
4174
|
+
},
|
|
4076
4175
|
]
|
|
4077
4176
|
}));
|
|
4078
4177
|
|
|
@@ -4091,7 +4190,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4091
4190
|
// These tools have filesystem/di.xml fallbacks — work without serve process
|
|
4092
4191
|
'magento_find_class', 'magento_find_method', 'magento_find_plugin',
|
|
4093
4192
|
'magento_find_observer', 'magento_find_di_wiring', 'magento_module_structure',
|
|
4094
|
-
'magento_batch', 'magento_find_config', 'magento_find_callers', 'magento_grep'];
|
|
4193
|
+
'magento_batch', 'magento_find_config', 'magento_find_callers', 'magento_grep', 'magento_read'];
|
|
4095
4194
|
if (warmupInProgress && !indexFreeTools.includes(name)) {
|
|
4096
4195
|
logToFile('REQ', `${name} → blocked (warmup: loading index)`);
|
|
4097
4196
|
return {
|
|
@@ -4892,7 +4991,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4892
4991
|
structure: structureOutput.categories
|
|
4893
4992
|
});
|
|
4894
4993
|
|
|
4895
|
-
|
|
4994
|
+
// Include README.md if it exists in the module directory
|
|
4995
|
+
let readmeText = '';
|
|
4996
|
+
if (results.length > 0) {
|
|
4997
|
+
// Find module root from first result path
|
|
4998
|
+
const firstPath = results[0].path || '';
|
|
4999
|
+
const moduleRoot = firstPath.split('/').slice(0, 3).join('/');
|
|
5000
|
+
if (moduleRoot) {
|
|
5001
|
+
const readmePath = path.join(config.magentoRoot, moduleRoot, 'README.md');
|
|
5002
|
+
try {
|
|
5003
|
+
const readme = readFileSync(readmePath, 'utf-8');
|
|
5004
|
+
readmeText = '\n\n## README.md\n\n' + readme.slice(0, 2000) + (readme.length > 2000 ? '\n...(truncated)' : '');
|
|
5005
|
+
} catch { /* no README */ }
|
|
5006
|
+
}
|
|
5007
|
+
}
|
|
5008
|
+
|
|
5009
|
+
return { content: [{ type: 'text', text: jsonOutput + readmeText }] };
|
|
4896
5010
|
}
|
|
4897
5011
|
|
|
4898
5012
|
case 'magento_analyze_diff': {
|
|
@@ -5906,13 +6020,28 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
5906
6020
|
}
|
|
5907
6021
|
break;
|
|
5908
6022
|
}
|
|
6023
|
+
case 'magento_read': {
|
|
6024
|
+
const filePath = path.join(config.magentoRoot, a.path);
|
|
6025
|
+
let fileContent;
|
|
6026
|
+
try { fileContent = readFileSync(filePath, 'utf-8'); } catch {
|
|
6027
|
+
text = `File not found: ${a.path}`;
|
|
6028
|
+
break;
|
|
6029
|
+
}
|
|
6030
|
+
const allLines = fileContent.split('\n');
|
|
6031
|
+
const s = Math.max((a.startLine || 1) - 1, 0);
|
|
6032
|
+
const e = a.endLine ? Math.min(a.endLine, allLines.length) : allLines.length;
|
|
6033
|
+
const sl = allLines.slice(s, e);
|
|
6034
|
+
text = sl.map((line, i) => `${s + i + 1}\t${line}`).join('\n');
|
|
6035
|
+
break;
|
|
6036
|
+
}
|
|
5909
6037
|
case 'magento_grep': {
|
|
5910
6038
|
const searchPath = a.path || '.';
|
|
5911
6039
|
const include = a.include || '*.php';
|
|
5912
6040
|
const maxRes = Math.min(a.maxResults || 30, 100);
|
|
6041
|
+
const batchCtx = a.context !== undefined ? a.context : 2;
|
|
5913
6042
|
const gArgs = ['-rn'];
|
|
5914
6043
|
if (a.ignoreCase) gArgs.push('-i');
|
|
5915
|
-
if (
|
|
6044
|
+
if (batchCtx > 0) gArgs.push('-C', String(batchCtx));
|
|
5916
6045
|
for (const pat of include.split(',').map(p => p.trim())) gArgs.push('--include=' + pat);
|
|
5917
6046
|
gArgs.push('--', a.pattern, searchPath);
|
|
5918
6047
|
let out;
|
|
@@ -5946,9 +6075,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
5946
6075
|
const searchPath = args.path || '.';
|
|
5947
6076
|
const include = args.include || '*.php';
|
|
5948
6077
|
const maxResults = Math.min(args.maxResults || 50, 200);
|
|
6078
|
+
const ctxLines = args.context !== undefined ? args.context : 2;
|
|
5949
6079
|
const grepArgs = ['-rn'];
|
|
5950
6080
|
if (args.ignoreCase) grepArgs.push('-i');
|
|
5951
|
-
if (
|
|
6081
|
+
if (ctxLines > 0) grepArgs.push('-C', String(ctxLines));
|
|
5952
6082
|
// Support multiple include patterns (e.g., "*.{php,xml}")
|
|
5953
6083
|
for (const pat of include.split(',').map(p => p.trim())) {
|
|
5954
6084
|
grepArgs.push('--include=' + pat);
|
|
@@ -5976,6 +6106,26 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
5976
6106
|
return { content: [{ type: 'text', text }] };
|
|
5977
6107
|
}
|
|
5978
6108
|
|
|
6109
|
+
case 'magento_read': {
|
|
6110
|
+
const root = config.magentoRoot;
|
|
6111
|
+
if (!root) return { content: [{ type: 'text', text: 'MAGENTO_ROOT not set.' }], isError: true };
|
|
6112
|
+
const filePath = path.join(root, args.path);
|
|
6113
|
+
let content;
|
|
6114
|
+
try { content = readFileSync(filePath, 'utf-8'); } catch (err) {
|
|
6115
|
+
return { content: [{ type: 'text', text: `File not found: ${args.path}` }], isError: true };
|
|
6116
|
+
}
|
|
6117
|
+
const allLines = content.split('\n');
|
|
6118
|
+
const start = Math.max((args.startLine || 1) - 1, 0);
|
|
6119
|
+
const end = args.endLine ? Math.min(args.endLine, allLines.length) : allLines.length;
|
|
6120
|
+
const sliced = allLines.slice(start, end);
|
|
6121
|
+
// Format with line numbers
|
|
6122
|
+
const numbered = sliced.map((line, i) => `${start + i + 1}\t${line}`).join('\n');
|
|
6123
|
+
let text = `## ${args.path}`;
|
|
6124
|
+
if (args.startLine || args.endLine) text += ` (lines ${start + 1}-${end})`;
|
|
6125
|
+
text += `\n\n${numbered}`;
|
|
6126
|
+
return { content: [{ type: 'text', text }] };
|
|
6127
|
+
}
|
|
6128
|
+
|
|
5979
6129
|
default:
|
|
5980
6130
|
return {
|
|
5981
6131
|
content: [{
|