From drupal-workflow
Enforces core Drupal 10+ rules for services, dependency injection, security including sanitization and access control, code quality, and testing verification. Always use when writing Drupal code.
npx claudepluginhub gkastanis/drupal-workflow --plugin drupal-workflowThis skill uses the workspace's default tool permissions.
Consolidated rules for Drupal development. Follow these for every implementation task.
Provides Drupal testing patterns with curl smoke tests, drush eval, and bash scripts to verify modules enabled, services exist, pages render, and config correct.
Runs code quality audits, security scans, test coverage, SOLID/DRY checks, and lints for Drupal (PHPStan, PHPMD, Psalm, Semgrep, Trivy, Gitleaks) and Next.js (ESLint, Jest, Semgrep, Trivy, Gitleaks) projects.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
Consolidated rules for Drupal development. Follow these for every implementation task.
When you see AUTOPILOT [type]: messages in hook output, you MUST stop what you are doing and follow the instruction. These are not suggestions — they are policy enforcement from the session autopilot. The intervention contains a specific tool call you must execute before making further edits. Ignoring an autopilot intervention is equivalent to ignoring a test failure.
\Drupal::* static calls in classes..info.yml, .module, /src (PSR-4), .services.yml.config/install/, not hook_install().<module>.services.yml with interface type-hints.final unless explicitly designed for extension.declare(strict_types=1) in all custom PHP files.private > protected > public.Verify Drupal service changes: after service or Twig changes, confirm service injections are correct, DB table and column names exist, and Twig filters actually exist in the project.
*.services.yml file directly.Drupal::hasService("module_name.service_name") before using any service.group_permission) even when docs or module name suggest plural (group_permissions).Xss::filter() or render arrays.Html::escape() for raw HTML output outside Twig.$entity->access('update') returning ALLOWED does NOT mean the route grants access. Other checkers (archived status, custom gates) may still deny.requirements YAML and trace each _*_access* checker class individually..env, settings.local.php, or credential files.Grep the codebase for remaining references after modifying functions, constants, or variables across files. This catches missed locations that would cause runtime errors.
Search for existing config constants before hardcoding values (e.g., * 8 for hours, * 5 for weekdays). Use system-configured values instead.
When removing or disabling something, remove from ALL locations (controller, template, Twig, JS, config) and grep to confirm nothing was missed.
array_filter(), array_map(), array_reduce() over foreach with nested if/break/continue.accessCheck(TRUE) on entity queries — never omit it.#cache metadata (tags, contexts, max-age) to render arrays.->t() or |t in Twig for TranslatableMarkup.\GuzzleHttp\Utils::jsonDecode/jsonEncode (not PHP's json_*).LoggerInterface methods (debug/info/warning/error) -- no custom debug flags.Drupal\my_module\Service) -- use logical groupings.$snake_case for local variables and function parameters.$lowerCamelCase for class properties/attributes..@Then, @Given, @When).Mandatory gate: Run tests, read output, confirm it proves your claim, THEN claim done. Never say "should work", "looks correct", or "I've implemented X" without test output.
# Status code + response size
ddev exec curl -s -o /dev/null -w "%{http_code} %{size_download}" -b <COOKIE> "http://localhost/<PATH>"
# Download full HTML
ddev exec curl -s -b <COOKIE> "http://localhost/<PATH>" > /tmp/page-output.html
# Get auth cookie
ddev drush uli --uid=1 --no-browser 2>/dev/null
Escaping rules: Use Drupal:: not \Drupal:: in single quotes. Use Exception not \Exception. No use statements. Keep PHP on one line.
ddev drush eval 'print json_encode(["exists" => Drupal::hasService("my.service")]);' 2>/dev/null
Store in scripts/tests/. Update scripts/tests/index.md when creating scripts.
These phrases signal unverified claims -- stop and test first:
ddev exec -- write a script file and execute it.drush uli + curl must be in the same script, same shell, with --uri=http://localhost so the cookie domain matches.edit-features, edit-moderation-state-0) not generic machine names.scripts/tests/ directory with index.chmod +x scripts/tests/*.sh.Naming conventions:
verify-{feature}.sh -- feature verification.debug-{feature}-{aspect}.php -- investigation.check-{aspect}.sh -- quick checks.list-{entities}.php -- data listing.fix-{issue}.php -- one-time fixes.