From drupilot
Contribute a ported module/theme back to Drupal.org via the modern issue-fork + Merge Request flow on git.drupalcode.org, or the legacy patch flow for unmigrated projects. USE THIS for the /drupilot-contribute flow and the drupal-contrib-publisher agent, when the user asks to "open a merge request / create an issue fork / make a patch / push my fix to drupal.org / contribute this upstream". Checks the prerequisites (drupal.org account + GitLab access + SSH key or PAT + git identity), creates the issue fork, branch and a correctly formatted commit, opens the MR (semi = confirm before every outward-facing action; auto = direct git push and API MR when the endpoint responds), degrades gracefully to manual instructions when the GitLab API is blocked, reminds the user about the Contribution Record (maintainers assign credit), and NEVER prints or persists the PAT.
How this skill is triggered — by the user, by Claude, or both
Slash command
/drupilot:drupal-contributionThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill publishes the result of a port/refactor back to Drupal.org. It
This skill publishes the result of a port/refactor back to Drupal.org. It implements PROMPT 3 in full. It only applies to a contrib subject (a project that exists on drupal.org); custom-only code has nowhere to contribute to.
Two hard safety rules run through everything:
.contrib.pat_env_var (default DRUPILOT_GITLAB_PAT). Never echo it,
never write it to a file, never put it in a URL that gets logged, never commit
it.semi mode, every outward-facing action is confirmed (push, MR
creation, anything hitting drupalcode/gitlab). The guard-contrib.sh
PreToolUse hook also asks for confirmation on git push/MR commands in semi
mode; do not try to bypass it.defaults.json -> .contrib.*.
Read them via config_json:
.contrib.gitlab_host (git.drupal.org, SSH),
.contrib.gitlab_https_host (git.drupalcode.org, HTTPS/web),.contrib.ssh_test_target, .contrib.ssh_keys_url, .contrib.pat_url,
.contrib.register_url, .contrib.drupalcode_access_url,
.contrib.pat_env_var.${CLAUDE_PLUGIN_ROOT} is the plugin root; the contrib leaf scripts live under
${CLAUDE_PLUGIN_ROOT}/scripts/contrib/.DRUPILOT_CONTRIB_MODE (default semi); the developer may
override per run.ROOT="${CLAUDE_PLUGIN_ROOT}"
. "$ROOT/scripts/lib/common.sh"
SUBJECT="$(cd "${1:-$PWD}" && pwd)"
NAME="$(subject_machine_name "$SUBJECT")" # candidate drupal.org project name
The project name is usually the module/theme machine name (or the parent project if it is a submodule). The prerequisite check (§2) confirms the project exists on drupal.org. If it does not, stop: there is nothing upstream to contribute to.
Run the contribute gate and the prerequisite checker:
bash "$ROOT/scripts/env/preflight.sh" --profile contribute
bash "$ROOT/scripts/contrib/check-prereqs.sh" --project "$NAME"
git + (SSH key OR PAT). Exit 2 -> show the
report and stop with no side effects.check-prereqs.sh additionally verifies git identity and, with --project,
whether the project exists on drupal.org
(curl -fsI https://www.drupal.org/project/NAME). It prints actionable guidance
and the links from .contrib.*..contrib.drupalcode_access_url). Without this you
cannot push.ssh-keygen -t ed25519; upload the public
key at .contrib.ssh_keys_url; test ssh -T $(config_json .contrib.ssh_test_target)
(expect Welcome to GitLab, @user!)..contrib.pat_url with scopes
read_repository, write_repository (+ api if using the API), expiry <=
1 year; export it as the .contrib.pat_env_var variable. The PAT acts as
the password — never printed, never stored.If git identity is missing and a TTY exists, set it (idempotent):
bash "$ROOT/scripts/contrib/setup-git.sh" # or --name "Real Name" --email [email protected]
The email should be the one linked to the drupal.org account.
Before any outward-facing action, gather:
4.0.x, 11.x), used for
rebasing and the tracking branch.semi (default) or auto, from DRUPILOT_CONTRIB_MODE unless the
developer overrides.The Drupal.org issue is created on the web — there is no public issue API, and the GitLab API is blocked by default — so drupilot does not submit the form. Instead it generates ready-to-paste content and the recommended values for the mandatory fields:
bash "$ROOT/scripts/contrib/make-issue.sh" --project "$NAME" --base BASE_VERSION \
--issue ISSUEID [--comment N] [--kind mr|patch] [--summary "what the port did"]
This writes NAME-issue-summary.md and NAME-issue-comment.md next to the
module and prints the recommended field values. Defaults
(DRUPILOT_ISSUE_*, env-overridable):
Drupal 11 compatibility.Task (a compatibility port, not a bug/feature).Normal.--base (e.g. 4.0.x → 4.0.x-dev).Code. This list is project-specific (e.g. some projects
add rules integration, token actions); tell the user to verify Code
against the project's own component list.DRUPILOT_ISSUE_ASSIGNEE=self, or unassigned.The issue summary is the standard Drupal.org template, but for a behavior-preserving port only the sections that apply are emitted — Problem/Motivation, Proposed resolution, Remaining tasks. The non-applicable sections (Steps to reproduce, User interface changes, API changes, Data model changes) are intentionally omitted, because a Phase 1 minimal port introduces none. Refine the generated prose with specifics from the assessment if useful, but keep that structure.
If the port kept ^10 || ^11 (Drupal 10 support is declared-not-verified,
per core-strategy.sh), pass --d10-unverified so "verify Drupal 10
compatibility" is added to Remaining tasks — the dual-support claim must not
be silently trusted in the issue.
Relay the recommended fields and the summary to the user to paste into the
issue, and keep NAME-issue-comment.md for the MR/patch step below.
Different projects use different conventions — detect, do not assume (PROMPT 3.4):
CONTRIBUTING.md / README (Grep for "commit message",
"Issue #", branch naming).{type}: #{issueID} One-line summary
By: user1
By: user2
Types: fix, feat, ci, docs, perf, refactor, test, task,
revert. By: lines use drupal.org usernames (no @).Issue #NNNN by user1, user2: Description.Pick the format the project actually uses. Branch name convention:
ISSUEID-short-description-in-lowercase-with-hyphens
(e.g. 3982435-ckeditor-5-compatibility).
bash "$ROOT/scripts/contrib/issue-fork.sh" --project "$NAME" --issue ISSUEID \
--base BASE_VERSION [--branch ISSUEID-short-description] [--workdir DIR]
This clones https://git.drupalcode.org/project/NAME.git, adds the issue remote
NAME-ISSUEID -> [email protected]:issue/NAME-ISSUEID.git, fetches, and checks
out either the existing tracking branch or a new ISSUEID-description branch. It
prints the next steps.
[email protected]:issue/NAME-ISSUEID.git,
HTTPS https://git.drupalcode.org/issue/NAME-ISSUEID.git.Apply the port/refactor changes in the working tree, then commit with the detected format (§4). Example, modern format:
git add -A
git commit -m "fix: #ISSUEID One-line summary"
bash "$ROOT/scripts/contrib/open-mr.sh" --project "$NAME" --issue ISSUEID \
--branch ISSUEID-short-description --base BASE_VERSION --mode semi \
--description-file NAME-issue-comment.md
--description-file makes the brief comment generated in §3.1 the MR
description (it falls back to a link to the issue when omitted). open-mr.sh
(mode defaults to DRUPILOT_CONTRIB_MODE):
origin/BASE_VERSION (PROMPT 3.2:
git fetch origin && git rebase origin/BASE_VERSION).confirm before the push and before opening the MR.glab / curl against the GitLab API (PAT read from the
.contrib.pat_env_var env var — never printed).git push NAME-ISSUEID ISSUEID-short-description.A .patch on the issue is conventional even when there is an MR — reviewers and
some CI expect one, and (most importantly) it lets a user apply the fix before
a maintainer merges the MR. So after the MR, always also:
Post the brief comment (NAME-issue-comment.md from §3.1) on the issue —
it summarizes the change and references the attached patch — and set the
status to "Needs review". The same text was used as the MR description.
Produce the contribution patch:
bash "$ROOT/scripts/contrib/make-patch.sh" --module "$NAME" --issue ISSUEID \
[--comment N] --base BASE_VERSION
This writes NAME-short-description-ISSUEID-COMMENT.patch
(git diff origin/BASE_VERSION) and verifies it applies cleanly onto
origin/BASE_VERSION — the version the patch targets. This check is a hard
gate: if the patch does not apply, the script discards it and exits
non-zero. Do not deliver a patch that does not apply; re-fetch/rebase onto
the correct base and re-run. Bump --comment per revision.
Attach the patch to the issue alongside the MR. This contribution patch is
distinct from the offline local preview patch (make-patch.sh --local,
named NAME-short-description.patch) that the port/refactor flow writes for
local testing (that one only warns if it does not apply).
If the project is not on the issue-fork workflow there is no MR; the patch is the
whole contribution. Generate the comment with --kind patch (§3.1), then run the
same patch script (no open-mr.sh step):
bash "$ROOT/scripts/contrib/make-patch.sh" --module "$NAME" --issue ISSUEID \
[--comment N] --base BASE_VERSION
It rebases onto origin/BASE_VERSION, writes
git diff origin/BASE_VERSION > NAME-short-description-ISSUEID-COMMENT.patch
(naming convention [module]-[short-description]-[issue]-[comment].patch) and
verifies it applies cleanly onto origin/BASE_VERSION — discarding it and
exiting non-zero if it does not (a patch that does not apply is useless to the
maintainer and to users testing it pre-merge). Then the developer attaches the
patch to the issue, posts the NAME-issue-comment.md comment, and sets "Needs
review".
The drupalcode GitLab API is standard (/api/v4/...) but blocked by default
by the Drupal Association; endpoints open only on request. Therefore:
auto, prefer direct git (clone/remote/push against
[email protected]:issue/NAME-ISSUEID.git) for the work that git can do.After opening the MR or producing the patch, always remind the user:
Credit is not granted by the commit. It is assigned in the issue's Contribution Record, and only maintainers assign it. Comment on the issue, set the status to "Needs review", and ensure your contributors are listed so maintainers can credit them. Do not attempt to "claim" credit.
This reminder is mandatory on every successful contribution, in both modes.
.contrib.pat_env_var; never echo
it, never write it to disk, never place it in a logged URL, never commit it. If
no PAT and no SSH key are present, stop at the gate (§2).guard-contrib.sh hook enforces this too; respect its ask decisions.issue-fork.sh must not duplicate remotes or
clobber an existing checkout; re-running open-mr.sh must detect an existing
MR/branch and not create a second one. The leaf scripts handle this — do not
reinvent the git plumbing.State: the project + issue, the flow used (modern MR + patch, or legacy patch only), the branch/commit, whether the MR was opened via API or left as a manual URL (with that URL), the contribution patch path (generated in both flows), and the Contribution Record reminder. Never include the PAT or any credential in the summary.
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