magector 2.3.0 → 2.3.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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Technology-aware MCP server for Magento 2 and Adobe Commerce with intelligent indexing and search.**
4
4
 
5
- Magector is a Model Context Protocol (MCP) server that deeply understands Magento 2 and Adobe Commerce. It builds a semantic vector index of your entire codebase — 18,000+ files across hundreds of modules — and exposes 28 tools that let AI assistants search, navigate, and understand the code with domain-specific intelligence. Instead of grepping for keywords, your AI asks *"how are checkout totals calculated?"* and gets ranked, relevant results in under 50ms, enriched with Magento pattern detection (plugins, observers, controllers, DI preferences, layout XML, and 20+ more).
5
+ Magector is a Model Context Protocol (MCP) server that deeply understands Magento 2 and Adobe Commerce. It builds a semantic vector index of your entire codebase — 18,000+ files across hundreds of modules — and exposes 34 tools that let AI assistants search, navigate, and understand the code with domain-specific intelligence. Instead of grepping for keywords, your AI asks *"how are checkout totals calculated?"* and gets ranked, relevant results in under 50ms, enriched with Magento pattern detection (plugins, observers, controllers, DI preferences, layout XML, and 20+ more).
6
6
 
7
7
  [![Rust](https://img.shields.io/badge/rust-1.75+-orange.svg)](https://www.rust-lang.org)
8
8
  [![Node.js](https://img.shields.io/badge/node-18+-green.svg)](https://nodejs.org)
@@ -58,7 +58,7 @@ The result: your AI assistant calls one MCP tool and gets ranked, pattern-enrich
58
58
  - **Complexity analysis** -- cyclomatic complexity, function count, and hotspot detection across modules
59
59
  - **Fast** -- 10-45ms queries via persistent serve process, batched ONNX embedding with adaptive thread scaling
60
60
  - **LLM description enrichment** -- generate natural-language descriptions of di.xml files using Claude, stored in SQLite, and prepend them to embedding text so descriptions influence vector search ranking (not just post-retrieval display)
61
- - **MCP server** -- 28 tools integrating with Claude Code, Cursor, and any MCP-compatible AI tool
61
+ - **MCP server** -- 34 tools integrating with Claude Code, Cursor, and any MCP-compatible AI tool
62
62
  - **Clean architecture** -- Rust core handles all indexing/search, Node.js MCP server delegates to it
63
63
 
64
64
  ---
@@ -70,7 +70,7 @@ flowchart LR
70
70
  subgraph node ["Node.js Layer"]
71
71
  direction TB
72
72
  G["CLI<br/>init · index · search · describe"]
73
- E["MCP Server<br/>28 tools · LRU cache"]
73
+ E["MCP Server<br/>34 tools · LRU cache"]
74
74
  F["Persistent Serve Process"]
75
75
  G --> F
76
76
  E --> F
@@ -369,7 +369,7 @@ npx magector index --force
369
369
 
370
370
  ## MCP Server Tools
371
371
 
372
- The MCP server exposes 28 tools for AI-assisted Magento 2 and Adobe Commerce development. All search tools return **structured JSON** with file paths, class names, methods, role badges, and content snippets -- enabling AI clients to parse results programmatically and minimize file-read round-trips.
372
+ The MCP server exposes 34 tools for AI-assisted Magento 2 and Adobe Commerce development. All search tools return **structured JSON** with file paths, class names, methods, role badges, and content snippets -- enabling AI clients to parse results programmatically and minimize file-read round-trips.
373
373
 
374
374
  ### Output Format
375
375
 
@@ -432,7 +432,10 @@ All search tools return structured JSON:
432
432
  | `magento_trace_flow` | Trace execution flow from an entry point (route, API, GraphQL, event, cron) -- maps controller → plugins → observers → templates in one call |
433
433
  | `magento_trace_dependency` | Trace DI graph for a class/interface -- preferences, plugins, virtualTypes, argument overrides (parses all di.xml, no index needed) |
434
434
  | `magento_find_event_flow` | Trace complete event chain: dispatchers → observers → handler PHP classes (parses events.xml + vector search) |
435
+ | `magento_find_event_dispatchers` | Find all PHP locations where a specific event is dispatched -- exact grep matching with method context and surrounding code **(v2.3)** |
435
436
  | `magento_find_layout` | Find layout XML files by handle or content -- lists blocks, containers, and referenceBlock declarations |
437
+ | `magento_trace_data_flow` | Trace how a data attribute flows: find all setters (magic setter, setData, addData) and getters (magic getter, getData) across PHP and XML **(v2.3)** |
438
+ | `magento_trace_call_chain` | Trace internal method call chain: follows `$this->method()`, `$this->dep->method()`, and `dispatch()` calls to build an execution tree **(v2.2)** |
436
439
 
437
440
  Auto-detects entry type from pattern (`/V1/...` → API, `snake_case` → event, `camelCase` → GraphQL, `path/segments` → route), or override with `entryType`. Use `depth: "shallow"` (entry + config + plugins) or `depth: "deep"` (adds observers, layout, templates, DI preferences).
438
441
 
@@ -442,6 +445,9 @@ Auto-detects entry type from pattern (`/V1/...` → API, `snake_case` → event,
442
445
  |------|-------------|
443
446
  | `magento_impact_analysis` | Analyze impact of changing a class -- finds use statements, DI references, direct instantiations, and type hints across the codebase |
444
447
  | `magento_find_test` | Find PHPUnit tests for a given class/method -- searches Test/ directories for coverage, mocks, and assertions |
448
+ | `magento_find_implementors` | Find all classes implementing a given PHP interface -- scans `implements` keywords and di.xml `<preference>` declarations **(v2.2)** |
449
+ | `magento_find_callers` | Find all call sites of a method across PHP and XML files -- `->method()` and `::method()` calls **(v2.2)** |
450
+ | `magento_find_di_wiring` | Complete DI picture for a class: preferences, plugins, constructor args, virtual types, and argument overrides from di.xml **(v2.2)** |
445
451
 
446
452
  ### Diagnostics
447
453
 
@@ -470,9 +476,22 @@ Auto-detects entry type from pattern (`/V1/...` → API, `snake_case` → event,
470
476
 
471
477
  - **Hybrid BM25+vector search** -- combines text frequency scoring with semantic vector similarity for better exact class name matches
472
478
  - **Query expansion** -- automatically expands queries with Magento domain synonyms (plugin → interceptor, checkout → cart/quote/totals, etc.)
473
- - **Module filtering** -- `moduleFilter` parameter on `magento_search` to limit results by vendor/module pattern (supports wildcards, e.g., `"Vendor_*"`)
479
+ - **Module filtering** -- `moduleFilter` parameter on `magento_search` to limit results by vendor/module pattern. Accepts a single string or array of strings. Supports wildcards, e.g., `"Vendor_*"` or `["Acme_PaymentGateway", "Acme_FreeShipping"]`
474
480
  - **Non-blocking reindex** -- old index stays usable during background rebuild; new index is built to a temp path and swapped in atomically on completion
475
481
 
482
+ ### Deep Code Analysis (v2.2)
483
+
484
+ - **`magento_find_implementors`** -- find all classes implementing a PHP interface (PHP `implements` + di.xml `<preference>`)
485
+ - **`magento_find_callers`** -- find all call sites of a method across PHP and XML files
486
+ - **`magento_find_di_wiring`** -- complete DI picture: preferences, plugins, constructor args, virtual types, argument overrides
487
+ - **`magento_trace_call_chain`** -- trace internal method execution chain: `$this->method()`, `$this->dep->method()`, and `dispatch()` calls with event→observer resolution
488
+
489
+ ### Data Flow & Event Tracing (v2.3)
490
+
491
+ - **`magento_trace_data_flow`** -- trace all setters and getters for a data attribute (magic methods, setData/getData, addData, constants, XML references). Answers "who writes/reads `custom_discounted_price_incl_tax` on `Quote\Address`?"
492
+ - **`magento_find_event_dispatchers`** -- grep-based exact search for all PHP locations dispatching a specific event, with method context and surrounding code. Complements `magento_find_event_flow` with higher precision.
493
+ - **`magento_find_plugin` area context** -- enriched output shows DI area (frontend/adminhtml/global/graphql) and explicit di.xml plugin registrations when `targetClass` is provided
494
+
476
495
  ### Tool Cross-References
477
496
 
478
497
  Each tool description includes "See also" hints to help AI clients chain tools effectively:
@@ -549,6 +568,12 @@ magento_trace_flow({ entryPoint: "checkout/cart/add", depth: "deep" })
549
568
  magento_trace_flow({ entryPoint: "/V1/products" })
550
569
  magento_trace_flow({ entryPoint: "placeOrder", entryType: "graphql" })
551
570
  magento_trace_flow({ entryPoint: "sales_order_place_after" })
571
+ magento_trace_data_flow({ attributeKey: "custom_discounted_price_incl_tax", modelClass: "Quote\\Address" })
572
+ magento_find_event_dispatchers({ eventName: "custom_discount_rule_validation_before" })
573
+ magento_find_implementors({ interfaceName: "ProductRepositoryInterface" })
574
+ magento_find_callers({ methodName: "collectTotals", className: "TotalsCollector" })
575
+ magento_find_di_wiring({ className: "CartManagementInterface" })
576
+ magento_trace_call_chain({ className: "Magento\\Quote\\Model\\QuoteManagement", methodName: "submit" })
552
577
  ```
553
578
 
554
579
  ---
@@ -629,7 +654,7 @@ cd rust-core && cargo run --release -- validate -m ./magento2 --skip-index
629
654
  magector/
630
655
  ├── src/ # Node.js source
631
656
  │ ├── cli.js # CLI entry point (npx magector <command>)
632
- │ ├── mcp-server.js # MCP server (20 tools, structured JSON output)
657
+ │ ├── mcp-server.js # MCP server (34 tools, structured JSON output)
633
658
  │ ├── binary.js # Platform binary resolver
634
659
  │ ├── model.js # ONNX model resolver/downloader
635
660
  │ ├── init.js # Full init command (index + IDE config)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magector",
3
- "version": "2.3.0",
3
+ "version": "2.3.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.3.0",
37
- "@magector/cli-linux-x64": "2.3.0",
38
- "@magector/cli-linux-arm64": "2.3.0",
39
- "@magector/cli-win32-x64": "2.3.0"
36
+ "@magector/cli-darwin-arm64": "2.3.1",
37
+ "@magector/cli-linux-x64": "2.3.1",
38
+ "@magector/cli-linux-arm64": "2.3.1",
39
+ "@magector/cli-win32-x64": "2.3.1"
40
40
  },
41
41
  "keywords": [
42
42
  "magento",
package/src/mcp-server.js CHANGED
@@ -2848,7 +2848,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
2848
2848
  { type: 'string' },
2849
2849
  { type: 'array', items: { type: 'string' } }
2850
2850
  ],
2851
- description: 'Filter results by vendor/module pattern(s). Accepts a single string or array of strings. Supports wildcards and vendor prefix matching. Uses "/" or "_" interchangeably as separator. Examples: "Vendor_*", ["drmax_paymentrestrictions", "drmax_module-free-shipping"], "Magento_Catalog".'
2851
+ description: 'Filter results by vendor/module pattern(s). Accepts a single string or array of strings. Supports wildcards and vendor prefix matching. Uses "/" or "_" interchangeably as separator. Examples: "Vendor_*", ["Acme_PaymentGateway", "Acme_FreeShipping"], "Magento_Catalog".'
2852
2852
  },
2853
2853
  expand: {
2854
2854
  type: 'boolean',
@@ -3330,7 +3330,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
3330
3330
  properties: {
3331
3331
  methodName: {
3332
3332
  type: 'string',
3333
- description: 'Method name to find callers for. Examples: "execute", "save", "collectTotals", "copySalesRuleIdsFromParentToDrmaxQuote"'
3333
+ description: 'Method name to find callers for. Examples: "execute", "save", "collectTotals", "copySalesRuleIdsFromParentToChildQuote"'
3334
3334
  },
3335
3335
  className: {
3336
3336
  type: 'string',
@@ -3362,7 +3362,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
3362
3362
  properties: {
3363
3363
  className: {
3364
3364
  type: 'string',
3365
- description: 'Full PHP class name (FQCN) to start tracing from. Examples: "DrmaxMarketplace\\OrderSplit\\Model\\CreateOrder\\CreateChildOrder", "Magento\\Quote\\Model\\QuoteManagement"'
3365
+ description: 'Full PHP class name (FQCN) to start tracing from. Examples: "Vendor\\OrderSplit\\Model\\CreateOrder\\CreateChildOrder", "Magento\\Quote\\Model\\QuoteManagement"'
3366
3366
  },
3367
3367
  methodName: {
3368
3368
  type: 'string',
@@ -3379,13 +3379,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
3379
3379
  },
3380
3380
  {
3381
3381
  name: 'magento_trace_data_flow',
3382
- description: 'Trace how a data attribute flows through the Magento codebase: find all PHP files that set (via magic setter, setData, addData) and get (via magic getter, getData) a specific attribute key. Shows which classes write vs read the attribute, in which methods, and whether XML configs reference it. Use this to understand data dependencies — e.g., who sets drmax_discounted_price_incl_tax on Quote\\Address and who reads it.',
3382
+ description: 'Trace how a data attribute flows through the Magento codebase: find all PHP files that set (via magic setter, setData, addData) and get (via magic getter, getData) a specific attribute key. Shows which classes write vs read the attribute, in which methods, and whether XML configs reference it. Use this to understand data dependencies — e.g., who sets custom_discounted_price_incl_tax on Quote\\Address and who reads it.',
3383
3383
  inputSchema: {
3384
3384
  type: 'object',
3385
3385
  properties: {
3386
3386
  attributeKey: {
3387
3387
  type: 'string',
3388
- description: 'The snake_case data attribute key to trace. Examples: "drmax_discounted_price_incl_tax", "base_grand_total", "drmax_free_shipping_price", "subtotal_with_discount"'
3388
+ description: 'The snake_case data attribute key to trace. Examples: "custom_discounted_price_incl_tax", "base_grand_total", "custom_free_shipping_price", "subtotal_with_discount"'
3389
3389
  },
3390
3390
  modelClass: {
3391
3391
  type: 'string',
@@ -3403,7 +3403,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
3403
3403
  properties: {
3404
3404
  eventName: {
3405
3405
  type: 'string',
3406
- description: 'Magento event name to find dispatchers for. Examples: "sales_order_place_after", "drmax_discount_rule_validation_before", "checkout_cart_add_product_complete"'
3406
+ description: 'Magento event name to find dispatchers for. Examples: "sales_order_place_after", "custom_discount_rule_validation_before", "checkout_cart_add_product_complete"'
3407
3407
  }
3408
3408
  },
3409
3409
  required: ['eventName']
@@ -1,672 +0,0 @@
1
- /**
2
- * Generates synthetic Magento code for validation testing
3
- */
4
-
5
- export const MOCK_MODULES = [
6
- 'Acme_Catalog',
7
- 'Acme_Checkout',
8
- 'Acme_Customer',
9
- 'Acme_Sales',
10
- 'Acme_Inventory'
11
- ];
12
-
13
- export function generateMockController(moduleName, controllerName, action = 'execute') {
14
- const [vendor, module] = moduleName.split('_');
15
- return {
16
- path: `app/code/${vendor}/${module}/Controller/${controllerName}.php`,
17
- content: `<?php
18
- declare(strict_types=1);
19
-
20
- namespace ${vendor}\\${module}\\Controller;
21
-
22
- use Magento\\Framework\\App\\Action\\HttpGetActionInterface;
23
- use Magento\\Framework\\Controller\\ResultFactory;
24
- use Magento\\Framework\\View\\Result\\Page;
25
-
26
- /**
27
- * ${controllerName} controller action
28
- */
29
- class ${controllerName} implements HttpGetActionInterface
30
- {
31
- private ResultFactory \$resultFactory;
32
-
33
- public function __construct(
34
- ResultFactory \$resultFactory
35
- ) {
36
- \$this->resultFactory = \$resultFactory;
37
- }
38
-
39
- /**
40
- * Execute action
41
- * @return Page
42
- */
43
- public function ${action}(): Page
44
- {
45
- /** @var Page \$page */
46
- \$page = \$this->resultFactory->create(ResultFactory::TYPE_PAGE);
47
- return \$page;
48
- }
49
- }
50
- `,
51
- metadata: {
52
- type: 'php',
53
- magentoType: 'Controller',
54
- module: moduleName,
55
- className: controllerName,
56
- methodName: action,
57
- isController: true
58
- }
59
- };
60
- }
61
-
62
- export function generateMockModel(moduleName, modelName, tableName) {
63
- const [vendor, module] = moduleName.split('_');
64
- return {
65
- path: `app/code/${vendor}/${module}/Model/${modelName}.php`,
66
- content: `<?php
67
- declare(strict_types=1);
68
-
69
- namespace ${vendor}\\${module}\\Model;
70
-
71
- use Magento\\Framework\\Model\\AbstractModel;
72
- use ${vendor}\\${module}\\Model\\ResourceModel\\${modelName} as ResourceModel;
73
-
74
- /**
75
- * ${modelName} model
76
- */
77
- class ${modelName} extends AbstractModel
78
- {
79
- protected \$_eventPrefix = '${tableName}';
80
-
81
- protected function _construct(): void
82
- {
83
- \$this->_init(ResourceModel::class);
84
- }
85
-
86
- protected function _beforeSave(): AbstractModel
87
- {
88
- // Custom before save logic
89
- return parent::_beforeSave();
90
- }
91
-
92
- protected function _afterLoad(): AbstractModel
93
- {
94
- // Custom after load logic
95
- return parent::_afterLoad();
96
- }
97
-
98
- public function getName(): ?string
99
- {
100
- return \$this->getData('name');
101
- }
102
-
103
- public function setName(string \$name): self
104
- {
105
- return \$this->setData('name', \$name);
106
- }
107
- }
108
- `,
109
- metadata: {
110
- type: 'php',
111
- magentoType: 'Model',
112
- module: moduleName,
113
- className: modelName,
114
- isModel: true,
115
- tableName
116
- }
117
- };
118
- }
119
-
120
- export function generateMockRepository(moduleName, entityName) {
121
- const [vendor, module] = moduleName.split('_');
122
- return {
123
- path: `app/code/${vendor}/${module}/Model/${entityName}Repository.php`,
124
- content: `<?php
125
- declare(strict_types=1);
126
-
127
- namespace ${vendor}\\${module}\\Model;
128
-
129
- use ${vendor}\\${module}\\Api\\${entityName}RepositoryInterface;
130
- use ${vendor}\\${module}\\Api\\Data\\${entityName}Interface;
131
- use ${vendor}\\${module}\\Model\\ResourceModel\\${entityName} as ResourceModel;
132
- use Magento\\Framework\\Api\\SearchCriteriaInterface;
133
- use Magento\\Framework\\Api\\SearchResultsInterface;
134
- use Magento\\Framework\\Exception\\CouldNotSaveException;
135
- use Magento\\Framework\\Exception\\NoSuchEntityException;
136
-
137
- /**
138
- * ${entityName} repository implementation
139
- */
140
- class ${entityName}Repository implements ${entityName}RepositoryInterface
141
- {
142
- private ResourceModel \$resourceModel;
143
- private ${entityName}Factory \$${entityName.toLowerCase()}Factory;
144
-
145
- public function __construct(
146
- ResourceModel \$resourceModel,
147
- ${entityName}Factory \$${entityName.toLowerCase()}Factory
148
- ) {
149
- \$this->resourceModel = \$resourceModel;
150
- \$this->${entityName.toLowerCase()}Factory = \$${entityName.toLowerCase()}Factory;
151
- }
152
-
153
- public function getById(int \$id): ${entityName}Interface
154
- {
155
- \$entity = \$this->${entityName.toLowerCase()}Factory->create();
156
- \$this->resourceModel->load(\$entity, \$id);
157
- if (!\$entity->getId()) {
158
- throw new NoSuchEntityException(__('Entity with id "%1" does not exist.', \$id));
159
- }
160
- return \$entity;
161
- }
162
-
163
- public function save(${entityName}Interface \$entity): ${entityName}Interface
164
- {
165
- try {
166
- \$this->resourceModel->save(\$entity);
167
- } catch (\\Exception \$e) {
168
- throw new CouldNotSaveException(__(\$e->getMessage()));
169
- }
170
- return \$entity;
171
- }
172
-
173
- public function delete(${entityName}Interface \$entity): bool
174
- {
175
- \$this->resourceModel->delete(\$entity);
176
- return true;
177
- }
178
-
179
- public function deleteById(int \$id): bool
180
- {
181
- return \$this->delete(\$this->getById(\$id));
182
- }
183
-
184
- public function getList(SearchCriteriaInterface \$searchCriteria): SearchResultsInterface
185
- {
186
- // Implementation
187
- }
188
- }
189
- `,
190
- metadata: {
191
- type: 'php',
192
- magentoType: 'Repository',
193
- module: moduleName,
194
- className: `${entityName}Repository`,
195
- isRepository: true,
196
- repositoryMethods: ['getById', 'save', 'delete', 'deleteById', 'getList']
197
- }
198
- };
199
- }
200
-
201
- export function generateMockPlugin(moduleName, targetClass, targetMethod) {
202
- const [vendor, module] = moduleName.split('_');
203
- const pluginName = `${targetClass}${targetMethod.charAt(0).toUpperCase() + targetMethod.slice(1)}Plugin`;
204
- return {
205
- path: `app/code/${vendor}/${module}/Plugin/${pluginName}.php`,
206
- content: `<?php
207
- declare(strict_types=1);
208
-
209
- namespace ${vendor}\\${module}\\Plugin;
210
-
211
- use ${targetClass.includes('\\') ? targetClass : `Magento\\Framework\\${targetClass}`};
212
-
213
- /**
214
- * Plugin for ${targetClass}::${targetMethod}
215
- */
216
- class ${pluginName}
217
- {
218
- /**
219
- * Before ${targetMethod}
220
- */
221
- public function before${targetMethod.charAt(0).toUpperCase() + targetMethod.slice(1)}(
222
- ${targetClass.split('\\').pop()} \$subject,
223
- ...\$args
224
- ): array {
225
- // Modify arguments before method execution
226
- return \$args;
227
- }
228
-
229
- /**
230
- * After ${targetMethod}
231
- */
232
- public function after${targetMethod.charAt(0).toUpperCase() + targetMethod.slice(1)}(
233
- ${targetClass.split('\\').pop()} \$subject,
234
- \$result
235
- ) {
236
- // Modify result after method execution
237
- return \$result;
238
- }
239
-
240
- /**
241
- * Around ${targetMethod}
242
- */
243
- public function around${targetMethod.charAt(0).toUpperCase() + targetMethod.slice(1)}(
244
- ${targetClass.split('\\').pop()} \$subject,
245
- callable \$proceed,
246
- ...\$args
247
- ) {
248
- // Execute before
249
- \$result = \$proceed(...\$args);
250
- // Execute after
251
- return \$result;
252
- }
253
- }
254
- `,
255
- metadata: {
256
- type: 'php',
257
- magentoType: 'Plugin',
258
- module: moduleName,
259
- className: pluginName,
260
- isPlugin: true,
261
- targetClass,
262
- targetMethod,
263
- pluginMethods: [
264
- { type: 'before', name: `before${targetMethod.charAt(0).toUpperCase() + targetMethod.slice(1)}` },
265
- { type: 'after', name: `after${targetMethod.charAt(0).toUpperCase() + targetMethod.slice(1)}` },
266
- { type: 'around', name: `around${targetMethod.charAt(0).toUpperCase() + targetMethod.slice(1)}` }
267
- ]
268
- }
269
- };
270
- }
271
-
272
- export function generateMockObserver(moduleName, eventName, observerName) {
273
- const [vendor, module] = moduleName.split('_');
274
- return {
275
- path: `app/code/${vendor}/${module}/Observer/${observerName}.php`,
276
- content: `<?php
277
- declare(strict_types=1);
278
-
279
- namespace ${vendor}\\${module}\\Observer;
280
-
281
- use Magento\\Framework\\Event\\Observer;
282
- use Magento\\Framework\\Event\\ObserverInterface;
283
- use Psr\\Log\\LoggerInterface;
284
-
285
- /**
286
- * Observer for ${eventName} event
287
- */
288
- class ${observerName} implements ObserverInterface
289
- {
290
- private LoggerInterface \$logger;
291
-
292
- public function __construct(LoggerInterface \$logger)
293
- {
294
- \$this->logger = \$logger;
295
- }
296
-
297
- /**
298
- * Execute observer
299
- * @param Observer \$observer
300
- * @return void
301
- */
302
- public function execute(Observer \$observer): void
303
- {
304
- \$event = \$observer->getEvent();
305
- \$this->logger->info('Event ${eventName} triggered');
306
- // Observer logic here
307
- }
308
- }
309
- `,
310
- metadata: {
311
- type: 'php',
312
- magentoType: 'Observer',
313
- module: moduleName,
314
- className: observerName,
315
- isObserver: true,
316
- eventName
317
- }
318
- };
319
- }
320
-
321
- export function generateMockDiXml(moduleName, configs) {
322
- const [vendor, module] = moduleName.split('_');
323
- let content = `<?xml version="1.0"?>
324
- <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
325
- xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
326
- `;
327
-
328
- for (const config of configs) {
329
- if (config.type === 'preference') {
330
- content += ` <preference for="${config.for}" type="${config.implementation}" />\n`;
331
- } else if (config.type === 'plugin') {
332
- content += ` <type name="${config.target}">
333
- <plugin name="${config.name}" type="${config.class}" sortOrder="${config.sortOrder || 10}" />
334
- </type>\n`;
335
- } else if (config.type === 'virtualType') {
336
- content += ` <virtualType name="${config.name}" type="${config.extends}">
337
- <arguments>
338
- <argument name="${config.argName}" xsi:type="string">${config.argValue}</argument>
339
- </arguments>
340
- </virtualType>\n`;
341
- }
342
- }
343
-
344
- content += `</config>`;
345
-
346
- return {
347
- path: `app/code/${vendor}/${module}/etc/di.xml`,
348
- content,
349
- metadata: {
350
- type: 'xml',
351
- magentoType: 'di.xml',
352
- module: moduleName,
353
- preferences: configs.filter(c => c.type === 'preference'),
354
- plugins: configs.filter(c => c.type === 'plugin'),
355
- virtualTypes: configs.filter(c => c.type === 'virtualType')
356
- }
357
- };
358
- }
359
-
360
- export function generateMockEventsXml(moduleName, events) {
361
- const [vendor, module] = moduleName.split('_');
362
- let content = `<?xml version="1.0"?>
363
- <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
364
- xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
365
- `;
366
-
367
- for (const event of events) {
368
- content += ` <event name="${event.name}">
369
- <observer name="${event.observerName}" instance="${vendor}\\${module}\\Observer\\${event.observerClass}" />
370
- </event>\n`;
371
- }
372
-
373
- content += `</config>`;
374
-
375
- return {
376
- path: `app/code/${vendor}/${module}/etc/events.xml`,
377
- content,
378
- metadata: {
379
- type: 'xml',
380
- magentoType: 'events.xml',
381
- module: moduleName,
382
- events: events.map(e => e.name),
383
- observers: events.map(e => ({ name: e.observerName, instance: `${vendor}\\${module}\\Observer\\${e.observerClass}` }))
384
- }
385
- };
386
- }
387
-
388
- export function generateMockWebApiXml(moduleName, routes) {
389
- const [vendor, module] = moduleName.split('_');
390
- let content = `<?xml version="1.0"?>
391
- <routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
392
- xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
393
- `;
394
-
395
- for (const route of routes) {
396
- content += ` <route url="${route.url}" method="${route.method}">
397
- <service class="${vendor}\\${module}\\Api\\${route.serviceClass}" method="${route.serviceMethod}" />
398
- <resources>
399
- <resource ref="${route.resource || 'anonymous'}" />
400
- </resources>
401
- </route>\n`;
402
- }
403
-
404
- content += `</routes>`;
405
-
406
- return {
407
- path: `app/code/${vendor}/${module}/etc/webapi.xml`,
408
- content,
409
- metadata: {
410
- type: 'xml',
411
- magentoType: 'webapi.xml',
412
- module: moduleName,
413
- apiRoutes: routes.map(r => ({ url: r.url, method: r.method })),
414
- apiServices: routes.map(r => ({ class: `${vendor}\\${module}\\Api\\${r.serviceClass}`, method: r.serviceMethod }))
415
- }
416
- };
417
- }
418
-
419
- export function generateMockGraphqlSchema(moduleName, types, queries, mutations) {
420
- const [vendor, module] = moduleName.split('_');
421
- let content = '';
422
-
423
- for (const type of types) {
424
- content += `type ${type.name} ${type.implements ? `implements ${type.implements}` : ''} {
425
- ${type.fields.map(f => ` ${f.name}: ${f.type}`).join('\n')}
426
- }\n\n`;
427
- }
428
-
429
- if (queries.length > 0) {
430
- content += `type Query {\n`;
431
- for (const q of queries) {
432
- content += ` ${q.name}(${q.args || ''}): ${q.returnType} @resolver(class: "${vendor}\\\\${module}\\\\Model\\\\Resolver\\\\${q.resolver}")\n`;
433
- }
434
- content += `}\n\n`;
435
- }
436
-
437
- if (mutations.length > 0) {
438
- content += `type Mutation {\n`;
439
- for (const m of mutations) {
440
- content += ` ${m.name}(${m.args || ''}): ${m.returnType} @resolver(class: "${vendor}\\\\${module}\\\\Model\\\\Resolver\\\\${m.resolver}")\n`;
441
- }
442
- content += `}\n`;
443
- }
444
-
445
- return {
446
- path: `app/code/${vendor}/${module}/etc/schema.graphqls`,
447
- content,
448
- metadata: {
449
- type: 'graphql',
450
- magentoType: 'graphql_schema',
451
- module: moduleName,
452
- types: types.map(t => t.name),
453
- queries: queries.map(q => q.name),
454
- mutations: mutations.map(m => m.name),
455
- resolvers: [...queries, ...mutations].map(x => x.resolver)
456
- }
457
- };
458
- }
459
-
460
- export function generateMockResolver(moduleName, resolverName, queryName) {
461
- const [vendor, module] = moduleName.split('_');
462
- return {
463
- path: `app/code/${vendor}/${module}/Model/Resolver/${resolverName}.php`,
464
- content: `<?php
465
- declare(strict_types=1);
466
-
467
- namespace ${vendor}\\${module}\\Model\\Resolver;
468
-
469
- use Magento\\Framework\\GraphQl\\Config\\Element\\Field;
470
- use Magento\\Framework\\GraphQl\\Query\\ResolverInterface;
471
- use Magento\\Framework\\GraphQl\\Schema\\Type\\ResolveInfo;
472
-
473
- /**
474
- * GraphQL resolver for ${queryName}
475
- */
476
- class ${resolverName} implements ResolverInterface
477
- {
478
- /**
479
- * @inheritdoc
480
- */
481
- public function resolve(
482
- Field \$field,
483
- \$context,
484
- ResolveInfo \$info,
485
- array \$value = null,
486
- array \$args = null
487
- ) {
488
- // Resolver implementation
489
- return [];
490
- }
491
- }
492
- `,
493
- metadata: {
494
- type: 'php',
495
- magentoType: 'GraphQlResolver',
496
- module: moduleName,
497
- className: resolverName,
498
- isResolver: true,
499
- queryName
500
- }
501
- };
502
- }
503
-
504
- export function generateMockBlock(moduleName, blockName) {
505
- const [vendor, module] = moduleName.split('_');
506
- return {
507
- path: `app/code/${vendor}/${module}/Block/${blockName}.php`,
508
- content: `<?php
509
- declare(strict_types=1);
510
-
511
- namespace ${vendor}\\${module}\\Block;
512
-
513
- use Magento\\Framework\\View\\Element\\Template;
514
- use Magento\\Framework\\View\\Element\\Template\\Context;
515
-
516
- /**
517
- * ${blockName} block
518
- */
519
- class ${blockName} extends Template
520
- {
521
- protected \$_template = '${vendor}_${module}::${blockName.toLowerCase()}.phtml';
522
-
523
- public function __construct(Context \$context, array \$data = [])
524
- {
525
- parent::__construct(\$context, \$data);
526
- }
527
-
528
- protected function _prepareLayout(): self
529
- {
530
- parent::_prepareLayout();
531
- return \$this;
532
- }
533
-
534
- protected function _toHtml(): string
535
- {
536
- return parent::_toHtml();
537
- }
538
-
539
- public function getItems(): array
540
- {
541
- return [];
542
- }
543
- }
544
- `,
545
- metadata: {
546
- type: 'php',
547
- magentoType: 'Block',
548
- module: moduleName,
549
- className: blockName,
550
- isBlock: true
551
- }
552
- };
553
- }
554
-
555
- export function generateMockCronJob(moduleName, cronName, schedule = '0 * * * *') {
556
- const [vendor, module] = moduleName.split('_');
557
- return {
558
- php: {
559
- path: `app/code/${vendor}/${module}/Cron/${cronName}.php`,
560
- content: `<?php
561
- declare(strict_types=1);
562
-
563
- namespace ${vendor}\\${module}\\Cron;
564
-
565
- use Psr\\Log\\LoggerInterface;
566
-
567
- /**
568
- * ${cronName} cron job
569
- */
570
- class ${cronName}
571
- {
572
- private LoggerInterface \$logger;
573
-
574
- public function __construct(LoggerInterface \$logger)
575
- {
576
- \$this->logger = \$logger;
577
- }
578
-
579
- public function execute(): void
580
- {
581
- \$this->logger->info('Cron job ${cronName} executed');
582
- // Cron logic here
583
- }
584
- }
585
- `,
586
- metadata: {
587
- type: 'php',
588
- magentoType: 'Cron',
589
- module: moduleName,
590
- className: cronName,
591
- isCron: true
592
- }
593
- },
594
- xml: {
595
- path: `app/code/${vendor}/${module}/etc/crontab.xml`,
596
- content: `<?xml version="1.0"?>
597
- <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
598
- xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
599
- <group id="default">
600
- <job name="${vendor.toLowerCase()}_${module.toLowerCase()}_${cronName.toLowerCase()}"
601
- instance="${vendor}\\${module}\\Cron\\${cronName}"
602
- method="execute">
603
- <schedule>${schedule}</schedule>
604
- </job>
605
- </group>
606
- </config>`,
607
- metadata: {
608
- type: 'xml',
609
- magentoType: 'crontab.xml',
610
- module: moduleName,
611
- cronJobs: [{ name: `${vendor.toLowerCase()}_${module.toLowerCase()}_${cronName.toLowerCase()}`, instance: `${vendor}\\${module}\\Cron\\${cronName}` }]
612
- }
613
- }
614
- };
615
- }
616
-
617
- /**
618
- * Generate a complete mock module with all components
619
- */
620
- export function generateCompleteMockModule(moduleName) {
621
- const [vendor, module] = moduleName.split('_');
622
- const files = [];
623
-
624
- // Controllers
625
- files.push(generateMockController(moduleName, 'Index', 'execute'));
626
- files.push(generateMockController(moduleName, 'View', 'execute'));
627
-
628
- // Models
629
- files.push(generateMockModel(moduleName, 'Item', `${vendor.toLowerCase()}_${module.toLowerCase()}_item`));
630
- files.push(generateMockRepository(moduleName, 'Item'));
631
-
632
- // Plugin
633
- files.push(generateMockPlugin(moduleName, 'Magento\\Catalog\\Model\\Product', 'getPrice'));
634
-
635
- // Observer
636
- files.push(generateMockObserver(moduleName, 'catalog_product_save_after', 'ProductSaveObserver'));
637
-
638
- // Block
639
- files.push(generateMockBlock(moduleName, 'ItemList'));
640
-
641
- // DI config
642
- files.push(generateMockDiXml(moduleName, [
643
- { type: 'preference', for: `${vendor}\\${module}\\Api\\ItemRepositoryInterface`, implementation: `${vendor}\\${module}\\Model\\ItemRepository` },
644
- { type: 'plugin', target: 'Magento\\Catalog\\Model\\Product', name: `${vendor.toLowerCase()}_${module.toLowerCase()}_product_price`, class: `${vendor}\\${module}\\Plugin\\ProductGetPricePlugin` }
645
- ]));
646
-
647
- // Events
648
- files.push(generateMockEventsXml(moduleName, [
649
- { name: 'catalog_product_save_after', observerName: `${vendor.toLowerCase()}_product_save`, observerClass: 'ProductSaveObserver' }
650
- ]));
651
-
652
- // Web API
653
- files.push(generateMockWebApiXml(moduleName, [
654
- { url: `/V1/${module.toLowerCase()}/items/:id`, method: 'GET', serviceClass: 'ItemRepositoryInterface', serviceMethod: 'getById' },
655
- { url: `/V1/${module.toLowerCase()}/items`, method: 'POST', serviceClass: 'ItemRepositoryInterface', serviceMethod: 'save' }
656
- ]));
657
-
658
- // GraphQL
659
- files.push(generateMockGraphqlSchema(moduleName,
660
- [{ name: `${module}Item`, fields: [{ name: 'id', type: 'Int!' }, { name: 'name', type: 'String' }] }],
661
- [{ name: `${module.toLowerCase()}Item`, args: 'id: Int!', returnType: `${module}Item`, resolver: 'ItemResolver' }],
662
- [{ name: `create${module}Item`, args: 'input: CreateItemInput!', returnType: `${module}Item`, resolver: 'CreateItemResolver' }]
663
- ));
664
- files.push(generateMockResolver(moduleName, 'ItemResolver', `${module.toLowerCase()}Item`));
665
-
666
- // Cron
667
- const cronJob = generateMockCronJob(moduleName, 'CleanupJob');
668
- files.push(cronJob.php);
669
- files.push(cronJob.xml);
670
-
671
- return files;
672
- }
@@ -1,326 +0,0 @@
1
- /**
2
- * Test queries with expected results for accuracy validation
3
- */
4
-
5
- export const TEST_QUERIES = [
6
- // Controller queries
7
- {
8
- id: 'ctrl-1',
9
- query: 'controller execute action',
10
- type: 'semantic',
11
- expectedTypes: ['Controller'],
12
- expectedPatterns: ['controller'],
13
- minResults: 1,
14
- category: 'controller'
15
- },
16
- {
17
- id: 'ctrl-2',
18
- query: 'Index controller',
19
- type: 'exact',
20
- expectedClasses: ['Index'],
21
- expectedTypes: ['Controller'],
22
- category: 'controller'
23
- },
24
-
25
- // Model queries
26
- {
27
- id: 'model-1',
28
- query: 'model beforeSave afterLoad',
29
- type: 'semantic',
30
- expectedTypes: ['Model'],
31
- expectedPatterns: ['model'],
32
- category: 'model'
33
- },
34
- {
35
- id: 'model-2',
36
- query: 'AbstractModel extends',
37
- type: 'semantic',
38
- expectedInContent: ['extends AbstractModel'],
39
- category: 'model'
40
- },
41
-
42
- // Repository queries
43
- {
44
- id: 'repo-1',
45
- query: 'repository getById save delete',
46
- type: 'semantic',
47
- expectedTypes: ['Repository'],
48
- expectedPatterns: ['repository'],
49
- category: 'repository'
50
- },
51
- {
52
- id: 'repo-2',
53
- query: 'ItemRepository',
54
- type: 'exact',
55
- expectedClasses: ['ItemRepository'],
56
- category: 'repository'
57
- },
58
- {
59
- id: 'repo-3',
60
- query: 'getList SearchCriteria',
61
- type: 'semantic',
62
- expectedInContent: ['getList', 'SearchCriteria'],
63
- category: 'repository'
64
- },
65
-
66
- // Plugin queries
67
- {
68
- id: 'plugin-1',
69
- query: 'plugin interceptor before after around',
70
- type: 'semantic',
71
- expectedTypes: ['Plugin'],
72
- expectedPatterns: ['plugin'],
73
- category: 'plugin'
74
- },
75
- {
76
- id: 'plugin-2',
77
- query: 'beforeGetPrice plugin',
78
- type: 'semantic',
79
- expectedInContent: ['beforeGetPrice', 'before'],
80
- category: 'plugin'
81
- },
82
- {
83
- id: 'plugin-3',
84
- query: 'around method interceptor',
85
- type: 'semantic',
86
- expectedInContent: ['around', 'proceed'],
87
- category: 'plugin'
88
- },
89
-
90
- // Observer queries
91
- {
92
- id: 'obs-1',
93
- query: 'observer execute event',
94
- type: 'semantic',
95
- expectedTypes: ['Observer'],
96
- expectedPatterns: ['observer'],
97
- category: 'observer'
98
- },
99
- {
100
- id: 'obs-2',
101
- query: 'catalog_product_save_after observer',
102
- type: 'semantic',
103
- expectedInContent: ['catalog_product_save_after'],
104
- category: 'observer'
105
- },
106
- {
107
- id: 'obs-3',
108
- query: 'ObserverInterface implement',
109
- type: 'semantic',
110
- expectedInContent: ['ObserverInterface'],
111
- category: 'observer'
112
- },
113
-
114
- // Block queries
115
- {
116
- id: 'block-1',
117
- query: 'block template phtml',
118
- type: 'semantic',
119
- expectedTypes: ['Block'],
120
- expectedPatterns: ['block'],
121
- category: 'block'
122
- },
123
- {
124
- id: 'block-2',
125
- query: '_toHtml _prepareLayout',
126
- type: 'semantic',
127
- expectedInContent: ['_toHtml', '_prepareLayout'],
128
- category: 'block'
129
- },
130
-
131
- // DI.xml queries
132
- {
133
- id: 'di-1',
134
- query: 'preference interface implementation',
135
- type: 'semantic',
136
- expectedFileTypes: ['xml'],
137
- expectedInContent: ['preference'],
138
- category: 'di'
139
- },
140
- {
141
- id: 'di-2',
142
- query: 'plugin configuration di.xml',
143
- type: 'semantic',
144
- expectedFileTypes: ['xml'],
145
- expectedInContent: ['plugin'],
146
- category: 'di'
147
- },
148
- {
149
- id: 'di-3',
150
- query: 'virtualType argument',
151
- type: 'semantic',
152
- expectedInContent: ['virtualType'],
153
- category: 'di'
154
- },
155
-
156
- // Events.xml queries
157
- {
158
- id: 'event-1',
159
- query: 'event observer configuration',
160
- type: 'semantic',
161
- expectedFileTypes: ['xml'],
162
- expectedInContent: ['event', 'observer'],
163
- category: 'events'
164
- },
165
- {
166
- id: 'event-2',
167
- query: 'catalog_product_save_after event config',
168
- type: 'exact',
169
- expectedInContent: ['catalog_product_save_after'],
170
- category: 'events'
171
- },
172
-
173
- // Web API queries
174
- {
175
- id: 'api-1',
176
- query: 'REST API endpoint route',
177
- type: 'semantic',
178
- expectedFileTypes: ['xml'],
179
- expectedInContent: ['route', 'service'],
180
- category: 'webapi'
181
- },
182
- {
183
- id: 'api-2',
184
- query: 'GET /V1/items',
185
- type: 'semantic',
186
- expectedInContent: ['GET', '/V1/'],
187
- category: 'webapi'
188
- },
189
-
190
- // GraphQL queries
191
- {
192
- id: 'gql-1',
193
- query: 'GraphQL resolver',
194
- type: 'semantic',
195
- expectedTypes: ['GraphQlResolver'],
196
- expectedPatterns: ['graphql_resolver'],
197
- category: 'graphql'
198
- },
199
- {
200
- id: 'gql-2',
201
- query: 'type Query mutation',
202
- type: 'semantic',
203
- expectedFileTypes: ['graphql'],
204
- expectedInContent: ['type', 'Query'],
205
- category: 'graphql'
206
- },
207
- {
208
- id: 'gql-3',
209
- query: 'ResolverInterface resolve Field',
210
- type: 'semantic',
211
- expectedInContent: ['ResolverInterface', 'resolve'],
212
- category: 'graphql'
213
- },
214
-
215
- // Cron queries
216
- {
217
- id: 'cron-1',
218
- query: 'cron job schedule execute',
219
- type: 'semantic',
220
- expectedTypes: ['Cron'],
221
- category: 'cron'
222
- },
223
- {
224
- id: 'cron-2',
225
- query: 'crontab.xml job instance',
226
- type: 'semantic',
227
- expectedFileTypes: ['xml'],
228
- expectedInContent: ['job', 'instance'],
229
- category: 'cron'
230
- },
231
-
232
- // Cross-cutting queries
233
- {
234
- id: 'cross-1',
235
- query: 'dependency injection constructor',
236
- type: 'semantic',
237
- expectedInContent: ['__construct'],
238
- minResults: 3,
239
- category: 'di_pattern'
240
- },
241
- {
242
- id: 'cross-2',
243
- query: 'LoggerInterface logging',
244
- type: 'semantic',
245
- expectedInContent: ['LoggerInterface', 'logger'],
246
- category: 'logging'
247
- },
248
- {
249
- id: 'cross-3',
250
- query: 'exception handling try catch',
251
- type: 'semantic',
252
- expectedInContent: ['Exception', 'throw'],
253
- category: 'error_handling'
254
- },
255
-
256
- // Module-specific queries
257
- {
258
- id: 'mod-1',
259
- query: 'Acme_Catalog module',
260
- type: 'module',
261
- expectedModule: 'Acme_Catalog',
262
- minResults: 3,
263
- category: 'module'
264
- },
265
-
266
- // Method-specific queries
267
- {
268
- id: 'method-1',
269
- query: 'function getById',
270
- type: 'method',
271
- expectedMethods: ['getById'],
272
- category: 'method'
273
- },
274
- {
275
- id: 'method-2',
276
- query: 'save method repository',
277
- type: 'method',
278
- expectedMethods: ['save'],
279
- category: 'method'
280
- }
281
- ];
282
-
283
- export const QUERY_CATEGORIES = {
284
- controller: { weight: 1.0, description: 'Controller action detection' },
285
- model: { weight: 1.0, description: 'Model and lifecycle hooks' },
286
- repository: { weight: 1.2, description: 'Repository pattern detection' },
287
- plugin: { weight: 1.3, description: 'Plugin/interceptor detection' },
288
- observer: { weight: 1.2, description: 'Observer pattern detection' },
289
- block: { weight: 1.0, description: 'Block class detection' },
290
- di: { weight: 1.2, description: 'DI configuration detection' },
291
- events: { weight: 1.1, description: 'Event configuration detection' },
292
- webapi: { weight: 1.2, description: 'Web API route detection' },
293
- graphql: { weight: 1.3, description: 'GraphQL schema/resolver detection' },
294
- cron: { weight: 1.0, description: 'Cron job detection' },
295
- di_pattern: { weight: 0.8, description: 'DI pattern recognition' },
296
- logging: { weight: 0.7, description: 'Logging pattern recognition' },
297
- error_handling: { weight: 0.7, description: 'Error handling patterns' },
298
- module: { weight: 1.0, description: 'Module filtering' },
299
- method: { weight: 1.0, description: 'Method search' }
300
- };
301
-
302
- /**
303
- * Generate edge case queries for stress testing
304
- */
305
- export function generateEdgeCaseQueries() {
306
- return [
307
- // Very short queries
308
- { id: 'edge-1', query: 'save', type: 'short', category: 'edge_short', minResults: 1, expectedMethods: ['save'] },
309
- { id: 'edge-2', query: 'get', type: 'short', category: 'edge_short', minResults: 1 },
310
-
311
- // Very long queries
312
- { id: 'edge-3', query: 'public function execute action controller', type: 'long', category: 'edge_long', minResults: 1, expectedPatterns: ['controller'] },
313
-
314
- // Technical jargon
315
- { id: 'edge-4', query: 'CRUD operations repository interface', type: 'jargon', category: 'edge_jargon', minResults: 1, expectedTypes: ['Repository'] },
316
-
317
- // Magento-specific terms - lower expectations
318
- { id: 'edge-5', query: 'service contract API', type: 'magento_specific', category: 'edge_magento', minResults: 1 },
319
-
320
- // Negative queries (should return few/no results)
321
- { id: 'edge-6', query: 'wordpress drupal laravel', type: 'negative', category: 'edge_negative', maxResults: 2 },
322
-
323
- // Mixed case
324
- { id: 'edge-7', query: 'REPOSITORY getbyid SAVE', type: 'case', category: 'edge_case', minResults: 1, expectedTypes: ['Repository'] }
325
- ];
326
- }