From drupilot
Use this skill for Phase 2 — the opt-in "Drupal 11 way" refactor — i.e. when running /drupilot-refactor or when the user explicitly asks to "modernize", "refactor to Drupal 11 best practices", "convert annotations to PHP 8 attributes", "add dependency injection / strict types", or "reach PHPStan level 6 / clean PHPCS". It rewrites the already-ported module to modern APIs: PHP 8 attributes for plugins, constructor dependency injection, strict types and final where appropriate, zero deprecations, PHPStan level 5–6, fully clean Drupal + DrupalPractice, while keeping the test suite green. This is the OPT-IN second phase — it assumes Phase 1 (minimal-port) already produced a D11-compatible module. Do NOT use it for first-pass compatibility; that is the minimal-port skill.
How this skill is triggered — by the user, by Claude, or both
Slash command
/drupilot:full-refactorThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Opt-in second phase. It assumes Phase 1 (`minimal-port`) already left the module
Opt-in second phase. It assumes Phase 1 (minimal-port) already left the module
compiling on Drupal 11 with no blocking deprecations. The goal now is
quality: modern Drupal 11 idioms, zero deprecations, PHPStan level 5–6, clean
Drupal + DrupalPractice, and a green test suite. Coordinate closely with the
drupal-test-engineer agent / test-adaptation skill — nothing breaks silently.
minimal-port. Do not mix phases.DRUPILOT_PHPSTAN_LEVEL_REFACTOR
(default 6) for this phase, not the Phase 1 default of 2.DRUPILOT_PHP_TARGET; see the
php-target-tuning skill (and the 8.5 caveat — never assume it).Apply these as rules with objective triggers, not as suggestions, so two refactors of the same module converge. When a rule's trigger is absent it is a no-op — that is the only discretion.
@Annotation (@Block, @FieldType, @FieldFormatter, @FieldWidget,
@Action, @QueueWorker, @EntityType, @RenderElement, …) is converted:
move ALL metadata into the matching #[...] attribute and drop the now-unused
use Drupal\...\Annotation\...;. Never leave a plugin half-converted.\Drupal:: → dependency injection. EVERY \Drupal::service('x') and
\Drupal:: accessor in a class that can receive services is injected:
ContainerFactoryPluginInterface::create() for plugins,
ContainerInjectionInterface::create() for controllers/forms, a services.yml
argument for services. Use constructor property promotion. Leave \Drupal::
only in procedural .module/.install hooks where DI is unavailable.declare(strict_types=1); in EVERY .php file under src/ and tests/.
Add parameter, return and property types wherever the type is known and
unambiguous.final by default. Mark a class final UNLESS it is abstract, an
interface, a *Base class, or another class in the module/its tests extends it.
(Plugins and services are normally final.)->accessCheck(TRUE), the messenger service,
typed config, current routing/event APIs). Target the refactor PHPStan level
clean with no deprecation notices.Do not introduce value objects/enums or other redesigns unless they are required to remove a deprecation: Phase 2 modernizes APIs, it does not redesign behavior.
Phase 2 introduces modern typed / final public APIs, which are
backwards-incompatible. Re-run the core-target helper in refactor mode:
bash "${CLAUDE_PLUGIN_ROOT}/scripts/analysis/core-strategy.sh" --subject "<path>" --phase refactor --json
--phase refactor asserts a BC break, so it recommends ^11 (drop Drupal 10)
and a major version bump — cut a new N+1.0.x branch rather than a minor on
the existing one. Apply the recommended core_version_requirement; for ^11 no
composer require.php is needed (core enforces PHP >= the target). If you
deliberately keep ^10 || ^11 (an explicit override), the helper returns a
require.php floor (DRUPILOT_REQUIRE_PHP_FLOOR: detect → the real floor, e.g.
>=8.1; target → >=<target>) — add it to composer.json. State the
version-bump implication (new major branch) in the summary.
Work one concern at a time. After each change, re-run the validate loop and the relevant tests:
# Coding standards: auto-fix then verify (must end clean):
bash "${CLAUDE_PLUGIN_ROOT}/scripts/analysis/run-phpcs.sh" --subject "<path>" --fix
# Static analysis at the refactor level (5-6):
bash "${CLAUDE_PLUGIN_ROOT}/scripts/analysis/run-phpstan.sh" --subject "<path>" \
--level "$(config_get DRUPILOT_PHPSTAN_LEVEL_REFACTOR 6)"
# Re-run the affected test group(s) (see test-adaptation for the full flow):
bash "${CLAUDE_PLUGIN_ROOT}/scripts/tests/run-phpunit.sh" --subject "<path>" --type all
run-phpcs.sh --fix runs phpcbf then phpcs --standard=Drupal,DrupalPractice
with the PROMPT §2.3 extension list. run-phpstan.sh runs against the
phpstan.neon at the Drupal root. Reference commands:
vendor/bin/phpstan analyse --level 6 web/modules/custom/MODULE
vendor/bin/phpcs --standard=Drupal,DrupalPractice web/modules/custom/MODULE
Increment the PHPStan level gradually if jumping straight to 6 produces an overwhelming list: tighten one level at a time (2 → 3 → … → 6), clearing findings at each step. This keeps each batch reviewable and the tests verifiable.
Coordinate with test-adaptation / the drupal-test-engineer agent:
run-phpunit.sh --coverage (--coverage-text /
--coverage-html).Before declaring the module refactored, all must hold:
info.yml D11-compatible (core_version_requirement correct).phpstan analyse --level 5-6 clean — zero deprecations, no errors at the
target level.phpcs --standard=Drupal,DrupalPractice clean.Phase 2 changes more code, so refresh the local patch so it reflects the
refactor (this is a standalone, anytime action, decoupled from contribution — the
/drupilot-patch command does the same):
bash "${CLAUDE_PLUGIN_ROOT}/scripts/contrib/make-patch.sh" --local --subject "<path>"
It rewrites MODULE-port-to-drupal-11.patch next to the module; add
--issue ID [--comment N] for an issue-comment-named one (still offline). The
merge-verified contribution patch stays the job of drupal-contribution.
Summarize (in English): each significant change and why (annotations →
attributes, \Drupal:: calls → DI, types/final added, deprecated APIs
replaced), the final PHPStan level reached, PHPCS status, the test results +
coverage, the refreshed local patch path, and any documented exception. The
module now follows the Drupal 11 way. End by putting the developer back in control
with the closing tabbed choice (the /drupilot-refactor command renders it): run
the tests, get the patch, or contribute (opt-in) — a candidate for
/drupilot-contribute.
create() requires the right interface
(ContainerFactoryPluginInterface for plugins vs ContainerInjectionInterface
for controllers/forms) — mismatching them breaks instantiation.declare(strict_types=1); can surface latent type bugs; run the tests right
after adding it.Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub thebrokenbrain/drupilot --plugin drupilot