agentleFS
muon-m2muon-m2/magento2-tools/skills/extension-point/SKILL.md

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
  1. Distinctive:

    Never edit the target class: All wiring is additive — the target file is read-only.

    line 22
  2. Distinctive:

    Lightest mechanism first: Prefer observer < plugin < preference.

    line 23
  3. Very specific:

    Area-scope the wiring: Use etc/di.xml for global scope.

    line 30
  4. Distinctive:

    Ask for any missing values in one batch.

    line 54
  5. Distinctive:

    3A — Write the failing test (RED): Before generating implementation code, write a test that expresses the expected behaviour and watch it …

    line 116
  6. Very specific:

    Follow context/references/tdd-discipline.md.

    line 128
  7. Distinctive:

    3B — Generate implementation (GREEN): Write the minimal code to make the 3A test pass, using the templates:

    line 131
  8. Very specific:

    Run the Phase 3A test with {ctx.runner} vendor/bin/phpunit and confirm it now

    line 148
  9. Very specific:

    Run review --diff (gate: zero Critical/High findings).

    line 150
  10. Very specific:

    Apply the shared module-hygiene baseline (required): After generating or modifying PHP files, run …

    line 151
  11. Very specific:

    Write a brief Markdown report to {output_root}/extension-points/{Vendor}_{Module}-{mode}-{slug}-{date}.md:

    line 163
  12. Distinctive:

    Test path + red→green evidence

    line 167
  13. Very specific:

    All templates follow the placeholder registry in context/references/placeholder-schema.md.

    line 242
  14. Very specific:

    No plugin on a final, private, static method or data interface.

    line 251
  15. Very specific:

    All generated files pass php -l / xmllint --noout.

    line 255

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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 — Observer class reacting to a dispatched event declared in events.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.
  • 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. 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.xml for global scope; use etc/{area}/di.xml or etc/{area}/events.xml for 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=Magento2 PHPCS is the gate. See context/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. See context/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

InputDefaultNotes
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
SortOrder10Integer; lower runs first
Areaglobalglobal, frontend, adminhtml, webapi_rest, graphql, crontab
Module(ask)Existing {Vendor}_{Module}

Observer mode

InputDefaultNotes
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
Areaglobalglobal, frontend, adminhtml, etc.
Module(ask)Existing {Vendor}_{Module}
Dispatched data shape(ask)Keys available via $observer->getData() / $observer->getEvent()

Preference mode

InputDefaultNotes
for (interface/class FQCN)(ask)Interface or class being replaced → {PreferenceFor}
Replacement class name(ask)PascalCase, placed in Model/
Areaglobalglobal, 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\Observer and the inner \Magento\Framework\Event, asserting execute() 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 -l on every generated .php file.
  • xmllint --noout on every generated .xml file.
  • Run the Phase 3A test with {ctx.runner} vendor/bin/phpunit and 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 a composer.json require entry, resolve a bounded constraint via ${CLAUDE_PLUGIN_ROOT}/skills/context/scripts/resolve-dep-constraint.sh <vendor/package> — never "*". See context/references/module-hygiene.md.
  • Consult ${CLAUDE_SKILL_DIR}/references/pitfalls.md before 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:upgrade command if registration.php / di.xml changed
  • 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, and docs/*.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, $proceed cost, 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}.php
  • templates/plugin-di.xml → etc/{area}/di.xml (merge)
  • templates/observer-class.php → Observer/{ObserverName}.php
  • templates/events.xml → etc/{area}/events.xml (merge)
  • templates/preference-di.xml → etc/{area}/di.xml (merge)
  • templates/preference-class.php → Model/{EntityName}.php
  • templates/test-plugin-unit.php → Test/Unit/Plugin/{PluginName}Test.php
  • templates/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).
  • around plugin is used only when before/after cannot express the logic.
  • No plugin on a final, private, static method 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 --diff returns zero Critical/High findings.

Common Pitfalls Handled

See ${CLAUDE_SKILL_DIR}/references/pitfalls.md for the full list. Key ones:

PitfallHow the skill avoids it
Plugging a final/private/static methodPhase 1 validates the method signature
Plugging a data interfacePhase 1 rejects FQCN matching */Api/Data/*Interface
Unnecessary aroundPhase 1 asks for justification; before/after suggested first
Global di.xml when only frontend needs itPhase 1 asks for area; defaults to global with warning
DB writes inside a hot event observerReference pitfalls.md; Phase 4 review gate

Related Skills

PhaseSkill
0context
Before (if module absent)module-create
(caller)feature Phase 5 — when a blueprint declares interception tasks
Afterreview --diff

Download now (ZIP)

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.