magector 2.8.1 → 2.9.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 +170 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magector",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.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.
|
|
37
|
-
"@magector/cli-linux-x64": "2.
|
|
38
|
-
"@magector/cli-linux-arm64": "2.
|
|
39
|
-
"@magector/cli-win32-x64": "2.
|
|
36
|
+
"@magector/cli-darwin-arm64": "2.9.1",
|
|
37
|
+
"@magector/cli-linux-x64": "2.9.1",
|
|
38
|
+
"@magector/cli-linux-arm64": "2.9.1",
|
|
39
|
+
"@magector/cli-win32-x64": "2.9.1"
|
|
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
|
|
|
@@ -4035,6 +4112,44 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
4035
4112
|
required: ['queries']
|
|
4036
4113
|
}
|
|
4037
4114
|
},
|
|
4115
|
+
{
|
|
4116
|
+
name: 'magento_grep',
|
|
4117
|
+
description: 'Exact text search (grep) across Magento PHP/XML/JS files. Unlike magento_search (semantic/vector), this finds EVERY occurrence of a literal string or regex pattern. Use for: finding all call sites of a method, all usages of a class name, all config references. Returns file:line:content for each match.',
|
|
4118
|
+
inputSchema: {
|
|
4119
|
+
type: 'object',
|
|
4120
|
+
properties: {
|
|
4121
|
+
pattern: {
|
|
4122
|
+
type: 'string',
|
|
4123
|
+
description: 'Text pattern to search for. Literal string or POSIX regex. Examples: "getPayment()->getMethod()", "removeButton", "sales_order_place_after", "class AddressConditions"'
|
|
4124
|
+
},
|
|
4125
|
+
path: {
|
|
4126
|
+
type: 'string',
|
|
4127
|
+
description: 'Subdirectory to search in (relative to MAGENTO_ROOT). Default: "." (entire codebase). Examples: "vendor/acme/", "app/code/", "vendor/magento/module-sales/"'
|
|
4128
|
+
},
|
|
4129
|
+
include: {
|
|
4130
|
+
type: 'string',
|
|
4131
|
+
description: 'File glob pattern to include. Default: "*.php". Examples: "*.xml", "*.{php,xml}", "*.js", "*.phtml"',
|
|
4132
|
+
default: '*.php'
|
|
4133
|
+
},
|
|
4134
|
+
ignoreCase: {
|
|
4135
|
+
type: 'boolean',
|
|
4136
|
+
description: 'Case-insensitive search (default: false)',
|
|
4137
|
+
default: false
|
|
4138
|
+
},
|
|
4139
|
+
maxResults: {
|
|
4140
|
+
type: 'number',
|
|
4141
|
+
description: 'Maximum number of matches to return (default: 50, max: 200)',
|
|
4142
|
+
default: 50
|
|
4143
|
+
},
|
|
4144
|
+
context: {
|
|
4145
|
+
type: 'number',
|
|
4146
|
+
description: 'Lines of context around each match (default: 0). Like grep -C.',
|
|
4147
|
+
default: 0
|
|
4148
|
+
}
|
|
4149
|
+
},
|
|
4150
|
+
required: ['pattern']
|
|
4151
|
+
}
|
|
4152
|
+
},
|
|
4038
4153
|
]
|
|
4039
4154
|
}));
|
|
4040
4155
|
|
|
@@ -4053,7 +4168,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4053
4168
|
// These tools have filesystem/di.xml fallbacks — work without serve process
|
|
4054
4169
|
'magento_find_class', 'magento_find_method', 'magento_find_plugin',
|
|
4055
4170
|
'magento_find_observer', 'magento_find_di_wiring', 'magento_module_structure',
|
|
4056
|
-
'magento_batch', 'magento_find_config', 'magento_find_callers'];
|
|
4171
|
+
'magento_batch', 'magento_find_config', 'magento_find_callers', 'magento_grep'];
|
|
4057
4172
|
if (warmupInProgress && !indexFreeTools.includes(name)) {
|
|
4058
4173
|
logToFile('REQ', `${name} → blocked (warmup: loading index)`);
|
|
4059
4174
|
return {
|
|
@@ -5868,6 +5983,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
5868
5983
|
}
|
|
5869
5984
|
break;
|
|
5870
5985
|
}
|
|
5986
|
+
case 'magento_grep': {
|
|
5987
|
+
const searchPath = a.path || '.';
|
|
5988
|
+
const include = a.include || '*.php';
|
|
5989
|
+
const maxRes = Math.min(a.maxResults || 30, 100);
|
|
5990
|
+
const gArgs = ['-rn'];
|
|
5991
|
+
if (a.ignoreCase) gArgs.push('-i');
|
|
5992
|
+
if (a.context) gArgs.push('-C', String(a.context));
|
|
5993
|
+
for (const pat of include.split(',').map(p => p.trim())) gArgs.push('--include=' + pat);
|
|
5994
|
+
gArgs.push('--', a.pattern, searchPath);
|
|
5995
|
+
let out;
|
|
5996
|
+
try {
|
|
5997
|
+
out = execFileSync('grep', gArgs, { cwd: config.magentoRoot, encoding: 'utf-8', timeout: 15000, maxBuffer: 5 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
5998
|
+
} catch (err) { out = err.stdout || ''; }
|
|
5999
|
+
const gLines = out.trim().split('\n').filter(Boolean);
|
|
6000
|
+
text = `Found ${gLines.length} matches${gLines.length > maxRes ? ` (showing ${maxRes})` : ''}:\n`;
|
|
6001
|
+
for (const gl of gLines.slice(0, maxRes)) text += gl + '\n';
|
|
6002
|
+
break;
|
|
6003
|
+
}
|
|
5871
6004
|
default:
|
|
5872
6005
|
text = `Unsupported batch tool: ${q.tool}`;
|
|
5873
6006
|
}
|
|
@@ -5884,6 +6017,42 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
5884
6017
|
return { content: [{ type: 'text', text }] };
|
|
5885
6018
|
}
|
|
5886
6019
|
|
|
6020
|
+
case 'magento_grep': {
|
|
6021
|
+
const root = config.magentoRoot;
|
|
6022
|
+
if (!root) return { content: [{ type: 'text', text: 'MAGENTO_ROOT not set.' }], isError: true };
|
|
6023
|
+
const searchPath = args.path || '.';
|
|
6024
|
+
const include = args.include || '*.php';
|
|
6025
|
+
const maxResults = Math.min(args.maxResults || 50, 200);
|
|
6026
|
+
const grepArgs = ['-rn'];
|
|
6027
|
+
if (args.ignoreCase) grepArgs.push('-i');
|
|
6028
|
+
if (args.context) grepArgs.push('-C', String(args.context));
|
|
6029
|
+
// Support multiple include patterns (e.g., "*.{php,xml}")
|
|
6030
|
+
for (const pat of include.split(',').map(p => p.trim())) {
|
|
6031
|
+
grepArgs.push('--include=' + pat);
|
|
6032
|
+
}
|
|
6033
|
+
grepArgs.push('--', args.pattern, searchPath);
|
|
6034
|
+
let output;
|
|
6035
|
+
try {
|
|
6036
|
+
output = execFileSync('grep', grepArgs, {
|
|
6037
|
+
cwd: root, encoding: 'utf-8', timeout: 30000,
|
|
6038
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
6039
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
6040
|
+
});
|
|
6041
|
+
} catch (err) {
|
|
6042
|
+
// grep returns exit code 1 when no matches found
|
|
6043
|
+
output = err.stdout || '';
|
|
6044
|
+
}
|
|
6045
|
+
const lines = output.trim().split('\n').filter(Boolean);
|
|
6046
|
+
const total = lines.length;
|
|
6047
|
+
const truncated = lines.slice(0, maxResults);
|
|
6048
|
+
let text = `## grep: \`${args.pattern}\`\n`;
|
|
6049
|
+
text += `Found **${total}** matches${total > maxResults ? ` (showing first ${maxResults})` : ''}\n\n`;
|
|
6050
|
+
for (const line of truncated) {
|
|
6051
|
+
text += line + '\n';
|
|
6052
|
+
}
|
|
6053
|
+
return { content: [{ type: 'text', text }] };
|
|
6054
|
+
}
|
|
6055
|
+
|
|
5887
6056
|
default:
|
|
5888
6057
|
return {
|
|
5889
6058
|
content: [{
|