Skip to content
Development
Skill

/composer-patch-generator

Generate and maintain patches for Composer-installed packages (Drupal contrib modules, WordPress packages, PHP libraries) using cweagans/composer-patches. Invoke when the user needs to modify a contrib or vendor package, mentions "composer patch", "patch a module", "patch a

From plugin
cms-cultivator
1726 skills1 agent
Install
$ npx -y skills add kanopi/cms-cultivator --skill composer-patch-generator --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/composer-patch-generator

Context preview

The summary Claude sees to decide when to auto-load this skill.

Generate and maintain patches for Composer-installed packages (Drupal contrib modules, WordPress packages, PHP libraries) using cweagans/composer-patches. Invoke when the user needs to modify a contrib or vendor package, mentions "composer patch", "patch a module", "patch a

SKILL.md

composer-patch-generator.SKILL.md
name: composer-patch-generator
description: Generate and maintain patches for Composer-installed packages (Drupal contrib modules, WordPress packages, PHP libraries) using cweagans/composer-patches. Invoke when the user needs to modify a contrib or vendor package, mentions "composer patch", "patch a module", "patch a contrib module", "cweagans", "diff -ruN", "extra.patches", or needs a package change that applies cleanly in CI. Covers generating diff -ruN patches, handling new files, wiring composer.json, and verifying the patch applies.

Composer Patch Generator

Generate reliable, CI-safe patches for Composer-managed packages you cannot edit directly (Drupal contrib modules, WordPress plugins/packages, PHP libraries).

Philosophy

Contrib and vendor code is not yours to fork. Patches let you carry a change on top of an upstream release, re-apply it on every `composer install`, and drop it the moment upstream ships a fix.

Core Beliefs

1. **Patches are temporary by design** — always describe the change so it can be removed when upstream merges it. 2. **CI is the source of truth** — a patch that applies locally but fails in CI is a broken patch. Generate for how CI applies, not how your laptop does. 3. **Minimal surface** — a patch should contain only your delta, never build artifacts, `.git` metadata, or tooling files. 4. **Never edit `vendor/` or `web/modules/contrib/` directly** — those dirs are git-ignored and rebuilt on install. The patch is the only durable artifact.

When to Use This Skill

Activate when the user:

  • Wants to change a Composer-installed package (contrib module, plugin, library).
  • Mentions "composer patch", "patch this module", "cweagans", "extra.patches".
  • Says a change "needs to be a patch" or "applies in CI but not locally" (or vice versa).
  • Needs to create, update, or debug an entry under `extra.patches` in `composer.json`.

Do **not** use for changes to custom (first-party) modules/themes — edit those directly.

Critical Rules (why patches fail in CI)

These are the failure modes that cause "works locally, fails on CI":

1. **Use `diff -ruN`, NOT `git diff`.** `cweagans/composer-patches` tries `git apply` only when the install dir has a `.git` folder. CI installs with `--prefer-dist` (archives, no `.git`), so it falls back to the `patch` command — which cannot parse git headers (`diff --git`, `new file mode 100644`, `index abc..def`). `diff -ruN` is universally compatible. 2. **Base the diff on the dist archive, not a `git clone`.** Drupal.org dist `.zip`/`.tar.gz` files differ from the git repo: they add `LICENSE.txt` and packaging metadata (version/datestamp lines appended to `.info.yml`). CI uses the dist. If you diff against a clone, those files show up as spurious "new file" hunks that fail when `patch` finds them already present. 3. **Never pass `--exclude` to `diff -ruN`.** It leaks into the header lines (`diff -ruN --ex a/file b/file`) and breaks `patch` parsing. Delete unwanted dirs (`.git`, `node_modules`) *before* diffing instead. 4. **Exclude tool artifacts.** After a patched install, the package dir contains a `PATCHES.txt` written by composer-patches. Never let it into your diff.

Workflow

1. Identify the package, version, and prior patches

# Exact installed version (drives which dist archive to download).
grep -E "^version|datestamp" web/modules/contrib/<module>/<module>.info.yml
# Or from the lock file:
grep -A2 '"name": "drupal/<module>"' composer.lock | head -3

# Any patches already applied (yours must stack cleanly on top of these).
grep -A6 '"drupal/<module>"' composer.json

2. Snapshot a pristine base BEFORE editing

Edit the installed files in place (so you can test live), and diff them against a clean snapshot of the *same version*.

WORK=/tmp/patch_gen && rm -rf "$WORK" && mkdir -p "$WORK"
# Preferred base = the dist archive CI will use:
curl -sL "https://ftp.drupal.org/files/projects/<module>-<version>.zip" -o "$WORK/dist.zip"
unzip -q "$WORK/dist.zip" -d "$WORK/base_dl"     # -> $WORK/base_dl/<module>
cp -r "$WORK/base_dl/<module>" "$WORK/base"

# If earlier patches exist, apply them to the base first so your diff is a
# clean delta ON TOP of them (order matters — composer applies in listed order):
( cd "$WORK/base" && patch -p1 --no-backup-if-mismatch < /path/to/earlier-patch.patch )

If you cannot download the dist (no network, private package), snapshot the current installed dir *before* you edit as the base — acceptable as long as no uncommitted edits exist yet and no unpatched files you'll touch differ from dist.

3. Make your edits to the installed package

Edit files under `web/modules/contrib/<module>/` (or `vendor/<pkg>/`) directly. Run the project's checks on the changed files (e.g. `composer phpcs`, `composer phpstan`, unit tests) before generating the patch.

4. Generate the patch with `diff -ruN`

# Mirror the edited install into a clean "modified" tree (drop noise + artifacts).
rsync -a --exclude='.git' --exclude='node_modules' \
  web/modules/contrib/<module>/ "$WORK/modified/"
rm -f "$WORK/modified/PATCHES.txt"          # composer-patches artifact — never patch it
rm -rf "$WORK/base/.git" "$WORK/modified/.git"

# Confirm ONLY the files you intended changed:
diff -qr "$WORK/base" "$WORK/modified"

# Emit the patch, normalizing paths to a/ … b/ (what `patch -p1` expects):
( cd "$WORK" && diff -ruN base/ modified/ \
  | sed 's|^--- base/|--- a/|; s|^+++ modified/|+++ b/|; \
         s|^diff -ruN base/|diff -ruN a/|; s| modified/| b/|' \
  ) > patches/<module>-<short-description>.patch

New files are handled automatically by `diff -ruN` (uses an epoch timestamp on the `---` line), unlike `git diff`'s unparseable `new file mode` header.

5. Wire it into `composer.json`

"extra": {
    "patches": {
        "drupal/<module>": {
            "Short description of the change (link to upstre
Read more
Ships withcms-cultivator

Specialist agents and auto-invoked skills for Drupal/WordPress development. Works in Claude Code, Claude Desktop, and OpenAI Codex. Full documentation: What changed in 2.0? CMS Cultivator now focuses on CMS development workflows.

Get the whole plugin

Other skills on cms-cultivator.