extension-point
Skill6 starsChanged 28 days agoMITView on GitHub
Wire behaviour onto an existing Magento 2 class without editing it. Three modes: - Never edit the target class. All wiring is additive — the target file is read-only. - Lightest mechanism first. Prefer observer < plugin < preference; reach for the lightest one that expresses the intent. - around only when before+after cannot express it. around wraps $proceed and blocks every other interceptor on the chain; it is expensive and fragile. - Never plugin final, private, or static methods.…
We split this skill into the rules it gives an agent
15 rules. About 0 are common, 6 are distinctive, and 9 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 22
Never edit the target class: All wiring is additive — the target file is read-only.
- Distinctive: line 23
Lightest mechanism first: Prefer observer < plugin < preference.
- Very specific: line 30
Area-scope the wiring: Use etc/di.xml for global scope.
- Distinctive: line 54
Ask for any missing values in one batch.
- Distinctive: line 116
3A — Write the failing test (RED): Before generating implementation code, write a test that expresses the expected behaviour and watch it …
- Very specific: line 128
Follow context/references/tdd-discipline.md.
- Distinctive: line 131
3B — Generate implementation (GREEN): Write the minimal code to make the 3A test pass, using the templates:
- Very specific: line 148
Run the Phase 3A test with {ctx.runner} vendor/bin/phpunit and confirm it now
- Very specific: line 150
Run review --diff (gate: zero Critical/High findings).
- Very specific: line 151
Apply the shared module-hygiene baseline (required): After generating or modifying PHP files, run …
- Very specific: line 163
Write a brief Markdown report to {output_root}/extension-points/{Vendor}_{Module}-{mode}-{slug}-{date}.md:
- Distinctive: line 167
Test path + red→green evidence
- Very specific: line 242
All templates follow the placeholder registry in context/references/placeholder-schema.md.
- Very specific: line 251
No plugin on a final, private, static method or data interface.
- Very specific: line 255
All generated files pass php -l / xmllint --noout.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Magento 2 Extension Point
Wire behaviour onto an existing Magento 2 class without editing it. Three modes:
- plugin — before/after/around interceptor declared in
di.xml - observer —
Observerclass reacting to a dispatched event declared inevents.xml - preference — replaces an interface or class binding in the DI container
Core Rules
- Never edit the target class. All wiring is additive — the target file is read-only.
- Lightest mechanism first. Prefer observer < plugin < preference; reach for the lightest one that expresses the intent.
aroundonly when before+after cannot express it.aroundwraps$proceedand blocks every other interceptor on the chain; it is expensive and fragile.- Never plugin
final,private, orstaticmethods. Magento's interceptor generator skips them silently, producing a proxy that doesn't intercept. Never plugin data interfaces (they are generated, not real classes). - Area-scope the wiring. Use
etc/di.xmlfor global scope; useetc/{area}/di.xmloretc/{area}/events.xmlfor area-specific behaviour. See${CLAUDE_SKILL_DIR}/references/area-scoping.md. - Coding style. Generated PHP follows PER-CS 3.0 as the baseline, with the Magento 2
coding standard taking precedence on any conflict;
--standard=Magento2PHPCS is the gate. Seecontext/references/php-coding-style.md. - Source of truth. Generate from templates → shared references → baked-in Magento 2 knowledge
→ official Magento/Adobe docs (live-fetched only when uncertain). Do NOT read, grep, or "study"
other modules under
app/code/vendor/*/Magento core to infer conventions, entity shapes, naming, or wiring. Narrow exceptions: the target module/class of this operation, and the specific contract of a module this code explicitly depends on. Affirm sources in the final report. Seecontext/references/source-of-truth.md.
Workflow
Phase 0 — Context Resolution
Invoke context (or run
${CLAUDE_PLUGIN_ROOT}/skills/context/scripts/resolve-context.sh); capture the
JSON as {ctx}. Abort if {ctx.magento_root} is unresolved. If the target module does
not exist, offer module-create first.
Phase 1 — Resolve Inputs
Ask for any missing values in one batch. Required inputs differ by mode:
Plugin mode
| Input | Default | Notes |
| Target FQCN | (ask) | Fully-qualified class name to intercept |
| Method | (ask) | Public, non-final, non-static method name |
| Plugin type | (ask) | before, after, or around |
| Plugin class name | (ask) | PascalCase, placed in Plugin/ |
| Plugin name (DI) | (ask) | snake_case identifier in di.xml |
| SortOrder | 10 | Integer; lower runs first |
| Area | global | global, frontend, adminhtml, webapi_rest, graphql, crontab |
| Module | (ask) | Existing {Vendor}_{Module} |
Observer mode
| Input | Default | Notes |
| Event name | (ask) | e.g. sales_order_save_after |
| Observer class name | (ask) | PascalCase, placed in Observer/ |
| Observer name (XML) | (ask) | snake_case identifier in events.xml |
| Area | global | global, frontend, adminhtml, etc. |
| Module | (ask) | Existing {Vendor}_{Module} |
| Dispatched data shape | (ask) | Keys available via $observer->getData() / $observer->getEvent() |
Preference mode
| Input | Default | Notes |
for (interface/class FQCN) | (ask) | Interface or class being replaced → {PreferenceFor} |
| Replacement class name | (ask) | PascalCase, placed in Model/ |
| Area | global | global, frontend, adminhtml, etc. |
| Module | (ask) | Existing {Vendor}_{Module} |
The generator derives {PreferenceForShort} as the unqualified short class name from the
{PreferenceFor} FQCN (i.e., the last segment after the final \). This short name is
used in the implements/extends clause of the generated PHP class after the use {PreferenceFor};
import, so that PHPCS Magento2 (ReferenceUsedNamesOnly) is satisfied. The FQCN form
{PreferenceFor} is still used as-is in preference-di.xml XML attribute values.
See ${CLAUDE_SKILL_DIR}/references/plugin-types.md,
${CLAUDE_SKILL_DIR}/references/observer-events.md,
${CLAUDE_SKILL_DIR}/references/preference-vs-plugin.md, and
${CLAUDE_SKILL_DIR}/references/area-scoping.md.
Phase 2 — Plan
Present every file to create or modify. Typical file sets per mode:
Plugin: Plugin/{PluginName}.php, etc/{area}/di.xml (merge),
Test/Unit/Plugin/{PluginName}Test.php
Observer: Observer/{ObserverName}.php, etc/{area}/events.xml (merge),
Test/Unit/Observer/{ObserverName}Test.php
Preference: Model/{EntityName}.php, etc/{area}/di.xml (merge)
Wait for "proceed."
Phase 3 — Test First, then Generate
3A — Write the failing test (RED). Before generating implementation code, write a test that expresses the expected behaviour and watch it fail for the right reason:
- Plugin: unit test mocking the subject class and asserting the interceptor transforms the argument or return value as intended.
- Observer: unit test mocking
\Magento\Framework\Event\Observerand the inner\Magento\Framework\Event, assertingexecute()acts on the event payload. - Preference: integration test asserting
\Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get({for})returns an instance of the replacement class. When no test DB is available, write a unit test asserting the replacement class can be instantiated.
Follow context/references/tdd-discipline.md. Run the test and confirm it
fails for the right reason (not a setup or autoload error).
3B — Generate implementation (GREEN). Write the minimal code to make the 3A test pass, using the templates:
${CLAUDE_SKILL_DIR}/templates/plugin-class.php${CLAUDE_SKILL_DIR}/templates/plugin-di.xml${CLAUDE_SKILL_DIR}/templates/observer-class.php${CLAUDE_SKILL_DIR}/templates/events.xml${CLAUDE_SKILL_DIR}/templates/preference-di.xml${CLAUDE_SKILL_DIR}/templates/preference-class.php
For test files: ${CLAUDE_SKILL_DIR}/templates/test-plugin-unit.php and
${CLAUDE_SKILL_DIR}/templates/test-observer-unit.php.
Phase 4 — Verify
php -lon every generated.phpfile.xmllint --noouton every generated.xmlfile.- Run the Phase 3A test with
{ctx.runner} vendor/bin/phpunitand confirm it now passes (it failed before 3B); run the module's suite to confirm nothing else broke. - Run
review --diff(gate: zero Critical/High findings). - Apply the shared module-hygiene baseline (required). After generating or modifying PHP
files, run
${CLAUDE_PLUGIN_ROOT}/skills/context/scripts/add-license-headers.sh {ctx.magento_root}/app/code/{Vendor}/{Module} {Vendor}to stamp the standard copyright header onto every new.php(idempotent — it skips files that already carry it). When adding acomposer.jsonrequireentry, resolve a bounded constraint via${CLAUDE_PLUGIN_ROOT}/skills/context/scripts/resolve-dep-constraint.sh <vendor/package>— never"*". Seecontext/references/module-hygiene.md. - Consult
${CLAUDE_SKILL_DIR}/references/pitfalls.mdbefore declaring Phase 4 done.
Phase 5 — Report
Write a brief Markdown report to
{output_root}/extension-points/{Vendor}_{Module}-{mode}-{slug}-{date}.md:
- Files generated
- Test path + red→green evidence
- Area scope chosen and rationale
bin/magento setup:upgradecommand ifregistration.php/di.xmlchanged- Cache flush hint (
bin/magento cache:flush)
Docs may now be stale. This change modified module code. Run
docs --module={Vendor}_{Module}to refresh the module's README, CHANGELOG, anddocs/*.md(technical reference, guides, and API references as applicable).
Inputs
/extension-point --mode=plugin --target=Magento\Checkout\Model\Cart --method=addProduct --type=after --module=Acme_Checkout
/extension-point --mode=observer --event=sales_order_save_after --module=Acme_Sales
/extension-point --mode=preference --for=Magento\Catalog\Api\ProductRepositoryInterface --module=Acme_Catalog [--docs-root=<path>]--docs-root=<path> — output-root override; see "Output root" below.
Outputs
{ctx.magento_root}/app/code/{Vendor}/{Module}/Plugin/{PluginName}.php # plugin mode
{ctx.magento_root}/app/code/{Vendor}/{Module}/etc/{area}/di.xml # plugin / preference mode
{ctx.magento_root}/app/code/{Vendor}/{Module}/Observer/{ObserverName}.php # observer mode
{ctx.magento_root}/app/code/{Vendor}/{Module}/etc/{area}/events.xml # observer mode
{ctx.magento_root}/app/code/{Vendor}/{Module}/Model/{EntityName}.php # preference mode
{ctx.magento_root}/app/code/{Vendor}/{Module}/Test/Unit/Plugin/{PluginName}Test.php
{ctx.magento_root}/app/code/{Vendor}/{Module}/Test/Unit/Observer/{ObserverName}Test.php
{output_root}/extension-points/{Vendor}_{Module}-{mode}-{slug}-{date}.md{output_root} defaults to .docs ({ctx.docs_root}), anchored at the project root, never
under {ctx.magento_root}, app/code, or a module dir. See the Artifact location rule in
context/SKILL.md.
Output root (--docs-root)
This skill accepts --docs-root=<path> (see
context/references/artifact-layout.md). When set, write the run report (and any
report artifacts) under <path>/extension-points/; otherwise default to
{ctx.docs_root}/extension-points/. feature passes this so a feature
run's reports collect under its folder.
Reference Files
${CLAUDE_SKILL_DIR}/references/plugin-types.md— before/after/around semantics, return-value and argument rules,$proceedcost, sortOrder.${CLAUDE_SKILL_DIR}/references/observer-events.md— common dispatched events, Observer/Event payload access, area-scoped events.xml.${CLAUDE_SKILL_DIR}/references/preference-vs-plugin.md— decision matrix, why preferences are a last resort, conflict risk.${CLAUDE_SKILL_DIR}/references/area-scoping.md— which di.xml/events.xml to use: global vs frontend/adminhtml/webapi_rest/graphql/crontab.${CLAUDE_SKILL_DIR}/references/pitfalls.md— final/private/static, data-interface plugins, around-proceed perf, observer idempotency, no DB writes in hot events.context/references/tdd-discipline.md— shared test-first RED/GREEN loop.context/references/php-coding-style.md— PER-CS + Magento coding style.context/references/naming.md— naming conventions.context/references/source-of-truth.md— source-of-truth hierarchy + the no-unrelated-module-scanning rule (allowed reads, live-doc fetch protocol, report affirmation).
Templates
templates/plugin-class.php→Plugin/{PluginName}.phptemplates/plugin-di.xml→etc/{area}/di.xml(merge)templates/observer-class.php→Observer/{ObserverName}.phptemplates/events.xml→etc/{area}/events.xml(merge)templates/preference-di.xml→etc/{area}/di.xml(merge)templates/preference-class.php→Model/{EntityName}.phptemplates/test-plugin-unit.php→Test/Unit/Plugin/{PluginName}Test.phptemplates/test-observer-unit.php→Test/Unit/Observer/{ObserverName}Test.php
All templates follow the placeholder registry in
context/references/placeholder-schema.md. Every token used must be in the
Registry there — tests/test-placeholder-tokens.sh enforces it.
Acceptance Criteria
- The target class is never edited.
- Correct mechanism chosen (lightest possible for the use case).
aroundplugin is used only when before/after cannot express the logic.- No plugin on a
final,private,staticmethod or data interface. - XML is area-scoped to the narrowest applicable scope.
- A failing unit test (or integration test for preference) was written and watched to fail before implementation, and passes after.
- All generated files pass
php -l/xmllint --noout. review --diffreturns zero Critical/High findings.
Common Pitfalls Handled
See ${CLAUDE_SKILL_DIR}/references/pitfalls.md for the full list. Key ones:
| Pitfall | How the skill avoids it |
Plugging a final/private/static method | Phase 1 validates the method signature |
| Plugging a data interface | Phase 1 rejects FQCN matching */Api/Data/*Interface |
Unnecessary around | Phase 1 asks for justification; before/after suggested first |
| Global di.xml when only frontend needs it | Phase 1 asks for area; defaults to global with warning |
| DB writes inside a hot event observer | Reference pitfalls.md; Phase 4 review gate |
Related Skills
| Phase | Skill |
| 0 | context |
| Before (if module absent) | module-create |
| (caller) | feature Phase 5 — when a blueprint declares interception tasks |
| After | review --diff |
In this repository35 other files it gives its agents.
- a11y-auditSkill
- admin-formSkill
- admin-listingSkill
- auditSkill
- breeze-adaptSkill
- breeze-compatSkill
- breeze-themeSkill
- cli-commandSkill
- contextSkill
- data-migrationSkill
- debugSkill
- deploySkill
- docsSkill
- eav-attributeSkill
- featureSkill
- fixSkill
- frontendSkill
- graphqlSkill
- i18nSkill
- indexerSkill
- lintSkill
- marketplaceSkill
- message-queueSkill
- module-createSkill
- perf-auditSkill
- releaseSkill
- remediateSkill
- reviewSkill
- securitySkill
- system-configSkill
- test-generateSkill
- triageSkill
- upgradeSkill
- webapiSkill
- widgetSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

